방프리

21.04.02 Chapter4. LINQ 활용 (Item 29) 본문

C#/Effective C#

21.04.02 Chapter4. LINQ 활용 (Item 29)

방프리 2021. 4. 2. 21:39

Item 29 : 컬렉션을 반환하기보다 이터레이터를 반환하는 것이 낫다

 

간단히 말해서 배열 전체를 반환하는 메서드를 생성하는 것보다 배열의 요소만을 반환하는 메서드를 구현하는 것이

최적화나 예외처리에 있어 좀 더 효율이 좋다.

실제로 컬렉션(배열)을 반환하더라도 일부분만 사용할 수도 있고, 예외처리 또한 컬렉션만 반환한다면 계속 

처리해주어야 하기 때문이다.

public static IEnumerable<char>
	GenerateAlphabetSubset(char first, char last)
{
    if (first < 'a')
    	throw new ArgumentException(
        	"first must be at least the letter a", nameof(first));
    if (first > 'z')
    	throw new ArgumentException(
        	"first must be no greater than z", nameof(first));
    if (last < first)
    	throw new ArgumentException(
        	"last must be at least as large as first", nameof(last));
    if (last > 'z')
    	throw new ArgumentException(
        	"last must not be past z", nameof(last));
    
    var letter = first;
    
    while(letter <= last)
    {
    	yield return letter;
        letter++;
    }
}

컴파일러는 위의 코드를 다음과 같은 유사한 코드를 생성한다.

public class EmbeddedSubsetIterator : IEnumerable<char>
{
    private readonly char first;
    private readonly char last;
    
    public EmbeddedSubsetIterator(char first, char last)
    {
    	this.first = first;
        this.last = last;
    }
    
    public IEnumerator<char> GetEnumerator() =>
    	new LetterEnumerator(first, last);
    
    IEnumerator IEnumerable.GetEnumerator() => 
    	new LetterEnumerator(first, last);
    
    public static IEnumerable<char> GenerateAlphabetSubset(
    	char first, char last) => 
        	new embeddedSubsetIterator(first, last);
    private class LetterEnumerator : IEnumerator<char>
    {
    	private readonly char first;
        private readonly char last;
        
        private bool isInitialized = false;
        
        public LetterEnumerator(char first, char last)
        {
        	this.first = first;
            this.last = last;
        }
        
        private char letter = (char)('a' - 1);
        
        public bool MoveNext()
        {
        	if (!isInitialized)
            {
            	if (first < 'a')
                	throw new ArgumentException(
                    "first must be at least the letter a",
                    nameof(first));
                if (first > 'z')
                	throw new ArgumentException(
                    "first must be no greater than z",
                    nameof(first));
                if (last < first)
                	throw new ArgumentException(
                    "last must be at least as alarge as first",
                    nameof(last));
                if (last > 'z')
                	throw new ArgumentException(
                    "last must not be past z",
                    nameof(last));
                letter = (char)(first - 1);
            }
            
            letter++;
            return letter <= last;
        }
        
        public char Current => letter;
        object IEnumerator.Current => letter;
        
        public void Reset() => isInitialized = false;
        
        void IDisposable.Dispose();
    }
}

이럴 경우 시퀸스의 첫 번째 요소가 요청될 때까지 매개변수의 값이 유효한지를 확인하는 코드가 수행되지 않는다.

즉, 개발자의 코드에서 버그가 나는 것이 아닌 시퀸스 사용에서 문제가 생긴다.

public static IEnumerable<char>
	GenerateAlphabetSubset(char first, char last)
{
    if (first < 'a')
    	throw new ArgumentException(
        	"first must be at least the letter a", nameof(first));
    if (first > 'z')
    	throw new ArgumentException(
        	"first must be no greater than z", nameof(first));
    if (last < first)
    	throw new ArgumentException(
        	"last must be at least as large as first", nameof(last));
    if (last > 'z')
    	throw new ArgumentException(
        	"last must not be past z", nameof(last));
    
    return GenerateAlphabetSubsetImpl(first, last);
}

private static IEnumerable<char> GenerateAlphabetSubsetImpl(
	char first, char last)
{
    var letter = first;
    while (letter <= last)
    {
    	yield return letter;
        letter++;
    }
}

이렇게 할 경우 private 메서드 진입 전 public에서 예외가 발생한다.

반대로 컬렉션을 반환해야할 때는 언제일까?

가장 적절한 것은 라이브러리를 만들어 사용자가 해당 컬렉션 데이터를 가공해야할 때 반환하는 것이 좋다. 

데이터를 그대로 주되 사용자가 어떻게 사용할지는 관심을 가지지 않는 것이다. 특히 API를 만드는 경우 데이터를 

사용자가 어떻게 사용할지 알 수 없기 때문에 컬렉션 를 반환하는 것이 좋다.

Comments