방프리
21.04.02 Chapter4. LINQ 활용 (Item 29) 본문
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를 만드는 경우 데이터를
사용자가 어떻게 사용할지 알 수 없기 때문에 컬렉션 를 반환하는 것이 좋다.
'C# > Effective C#' 카테고리의 다른 글
21.04.15 Chapter4. LINQ 활용 (Item 31) (0) | 2021.04.16 |
---|---|
21.04.03 Chapter4. LINQ 활용 (Item 30) (0) | 2021.04.03 |
21.03.31 Chapter3. 제네릭 활용 (Item 28) (0) | 2021.03.31 |
21.03.30 Chapter3. 제네릭 활용 (Item 27) (0) | 2021.03.30 |
21.03.30 Chapter3. 제네릭 활용 (Item 26) (0) | 2021.03.30 |
Comments