C# IEnumerable 및 yield - slog

C#의 IEnumerable과 LINQ, yield 키워드로 메모리 재할당 없는 목록 순회하기

6개의 좋아요

Enumerable의 LINQ스타일 확장메소드를 보면 목록을 조회하거나 정렬할 때 목록을 반환하기 위해 메모리를 재할당 하지 않습니다. 비밀은 yield 키워드인데요, yield 키워드를 사용하면, 목록을 소비하는 코드와 반환하는 코드를 분리할 수 있는데요, 코드상으로는 분리되어 있지만, 실제 코드 흐름은 두가지 포인트가 결합됩니다.

A.B.C.ToList() 형태로 yield로 반환하는 IEnumerable 목록을 반환하는 메소드를 결합하면, 메모리 재할당 없이 순회목록이 yield에 의해 우아하게 결합합니다.

2개의 좋아요

IEnumerable과 yield, 그리고 결합과 await foreach를 적절히 잘 이용하면, 상태의 전이를 코드로 우아하게 짤 수 있습니다.

2개의 좋아요

이런 특징들이 함수형 언어의 영향이라고 개인적으로 생각하고 있는데, 명확하게 이거다 라고 할 정도로 정리는 못했습니다. 시간 날 때 코드로 표현하겠습니다.

2개의 좋아요

IEnumerable의 Concat 메소드.
목록의 결합은 실제로 yield return의 연속입니다.

        public static IEnumerable<TSource> Concat<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second) {
            if (first == null) throw Error.ArgumentNull("first");
            if (second == null) throw Error.ArgumentNull("second");
            return ConcatIterator<TSource>(first, second);
        }

        static IEnumerable<TSource> ConcatIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second) {
            foreach (TSource element in first) yield return element;
            foreach (TSource element in second) yield return element;
        }
2개의 좋아요

delegate를 이용하면 selector를 구현할 수 있습니다. Enumerable의 LINQ 메소드를 보면, selectorFunc<TSource, TResult>형태로 사용했음을 알 수 있습니다. 예를들어,

        public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) {
            if (source == null) throw Error.ArgumentNull("source");
            if (selector == null) throw Error.ArgumentNull("selector");
            if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Select(selector);
            if (source is TSource[]) return new WhereSelectArrayIterator<TSource, TResult>((TSource[])source, null, selector);
            if (source is List<TSource>) return new WhereSelectListIterator<TSource, TResult>((List<TSource>)source, null, selector);
            return new WhereSelectEnumerableIterator<TSource, TResult>(source, null, selector);
        }

의 경우, selector를 제네릭과 같이 사용해서 Select메소드와 selector의 종속성을 제거합니다.

2개의 좋아요

목록을 반환 할 때 빈 목록을 new List<TItem>() 형태로 곧잘 반환합니다. 하지만 이러면 메소드가 호출될 때마다 인스턴스가 생성되므로, Enumerable.Empty<TItem>()가 좋은 선택일 수 있습니다. 내부적으로는 해당 타입을 정적으로 할당하여 재사용 합니다.

    internal class EmptyEnumerable<TElement>
    {
        public static readonly TElement[] Instance = new TElement[0];
    }
2개의 좋아요

LINQ에 익숙해지면 selector에 의해 자료를 묶었다 풀었다 할 수 있습니다.

GroupBy : selector 기준으로 묶음
SelectMany : selector 기준으로 품

예)

list = list.GroupBy(x => x.성별).SelectMany(x => x.OrderBy(y => y.등수).Take(2));
// 결과 : 남자중 성적이 가장 좋은 2명 정보, 여자 중 성적이 가장 좋은 2명 정보
1개의 좋아요

runtime/src/libraries/System.Linq/src/System/Linq at master · dotnet/runtime (github.com)

1개의 좋아요

좋은 설명 감사합니다 :heart_eyes:

1개의 좋아요