본문 바로가기
책/Effective C#

Effective C# - Item31 시퀀스에 사용할 수 있는 조합 가능한 API를 작성하라

by 코딩하는 돼징 2024. 3. 8.
반응형

반복 구문이 필요한 경우

단일 요소를 처리하기보다 여러 요소로 구성된 시퀀스를 처리하는 알고리즘을 작성하기 위한 경우가 대부분이다. 반복 구문으로 작업을 진행하는 경우 효율성에 문제가 있다. 전체 컬렉션을 대상으로 단 하나의 작업만을 수행하는 경우는 거의 없고 최초 원본 컬렉션에 대해 다양한 작업을 여러 단계를 걸쳐 수행한 후에야 비로소 원하는 결과를 얻을 수 있는 경우가 대부분이기 때문이다.

대안

개별 요소에 대해 수행해야 하는 모든 작업을 분리된 메서드로 작성한 후 루프 내에서 이 메서드를 호출하는 방법이 있다. 이렇게 코드를 작성하면 메서드의 재사용 가능성이 낮아진다. 여러 단계의 작업을 단번에 수행하도록 작성된 메서드보다 각각의 작업을 개별적으로 수행하는 메서드들이 재사용 가능성이 훨씬 높기 때문이다.


Item30에서 언급했듯이 시퀀스를 다루는 메서드는 이터레이터를 활용하여 작성할 수 있으며 결과가 필요한 시점에 맞춰 메서드가 수행되도록 할 수 있다.

이터레이터 메서드

단일의 시퀀스(IEnumerable<T>로 표현되는)를 입력으로 취하고 그 결과로도 단일의 시퀀스(다른 IEnumerable<T>)를 반환하는 메서드를 말하는데 이러한 메서드를 작성할 때 yield return을 사용하면 메서드 내에서 시퀀스 내의 개별 요소를 저장하기 위해 별도의 저장소가 필요가 없다. 왜냐하면 필요한 시점에 입력 시퀀스 상에서 다음 요소를 가져오고 출력 결과가 반드시 필요한 시점에 출력 시퀀스로 결과를 내보내기 때문이다.

매개변수 타입과 반환 타입을 IEnumerable<T>로 정의하는 경우 이점

01 재사용성에 좋음

다양한 방식으로 조합하여 사용할 수 있는 알고리즘이 만들어지므로 재사용성이 좋아진다.

02 런타임 개선

여러 메서드를 하나로 조합하면 전체 시퀀스를 한 번만 순회하면서 조합된 메서드 세트를 수행할 수 있으므로 런타임의 효율이 개선된다.

03 알고리즘 조합 가능성 높아짐

N번째 요소가 요청됐을 때 비로소 N번째 결과를 생성하기 위해 코드를 실행한다. 이러한 지연 수행 모델(deferrred excution model)덕분에 전통적인 방식으로 알고리즘은 구현한 경우 반드시 필요한 추가 저장소를 사용하지 않을 수 있으며 각 메서드가 구현하고 있는 알고리즘의 조합 가능성도 높아진다.


예시

01 정수 배열을 받아와서 중복값을 제거한 후 출력하는 메서드

public static void Unique(IEnumerable<int> nums)
{
    var uniqueVals = new HasSet<int>();
    
    foreach(var num in nums)
    {
        if(!UniqueVals.Contains(num))
        {
            uniqueVals.Add(num);
            WriteLine(num);
        }
    }
}

02 (중복이 제거된) 고유한 값을 담고 있는 시퀀스 반환

public static void Unique(IEnumerable<int> nums)
{
    var uniqueVals = new HasSet<int>();
    
    foreach(var num in nums)
    {
        if(!UniqueVals.Contains(num))
        {
            uniqueVals.Add(num);
            yield return num;
        }
    }
}

 

아래와 같이 사용할 수 있다.

foreach(var num in Unique(nums))
    WriteLine(num);

yield return문 동작 방식

1) 값을 반환한 후 현재 메서드의 실행 상태를 일시 중지시키고 호출자에게 값을 반환한다. 이때 내부적으로 사용하는 이터레이터는 현재의 위치와 상태 정보를 저장한다.

2) 전체 시퀀스에 대하여 수행해야 할 메서드는 입력과 출력이 모두 이터레이터다. 이를 통해 반복 가능한 컬렉션을 효과적으로 처리할 수 있다.

3) 순환 과정은 내부적으로 입력 시퀀스의 현재 위치를 계속 갱신해가면서 출력 시퀀스에 차례차례 그 결과를 반환하는 과정이다. 이러한 메서드를 컨티뉴어블 메서드(Continuable Method)라고도 한다. 현재의 수행 상태를 보전하고 있어서 메서드로 재진입 시 이전에 수행한 코드 이후부터 수행을 이어갈 수 있는 메서드이다.

그러므로 yield return문은 메서드를 호출하는 쪽에서 필요한 만큼의 결과를  처리할 수 있도록 하며 중간 결과를 생성하고 반환하는데 효과적으로 사용될 수 있다.


결론

각각의 이터레이터 메서드는 입력 시ㅝㄴ스의 개별 요소에 대해 간단한 작업만을 수행한 후 출력 시퀀스로 새로운 요소를 내보낸다. 따라서 각각의 이터레이터 메서드는 매우 작게 장성할 수 있다. 이터레이터 메서드를 하나의 입력 시퀀스와 하나의 출력 시퀀스를 갖도록 작성하면 조합하기가 쉬워진다.

 

 

 

 

 

 

본 게시글은 Effective C#을 읽고 정리하였습니다.

 

 

 

 

 

반응형

댓글