일반적인 IEnumerable을 사용할 수 있는데 yield 키워드를 사용하는 이유는 무엇입니까?


171

이 코드가 주어지면 :

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            yield return item;
    }
}

왜 이런 식으로 코딩하지 않아야합니까? :

IEnumerable<object> FilteredList()
{
    var list = new List<object>(); 
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            list.Add(item);
    }
    return list;
}

yield키워드 의 기능을 이해합니다 . 컴파일러에게 특정 종류 (반복자)를 만들도록 지시합니다. 그러나 왜 그것을 사용합니까? 코드가 약간 적을뿐 아니라 어떻게해야합니까?


28
나는이 그냥 예입니다 실현,하지만 정말 코드가 있어야 다음과 같이 기록 : FullList.Where(IsItemInPartialList):)
BlueRaja - 대니 Pflughoeft

답변:


241

를 사용 yield하면 수집이 지연됩니다.

처음 5 개 항목 만 필요하다고 가정 해 봅시다. 당신의 방법 은, 처음 다섯 항목을 얻으려면 전체 목록 을 반복해야 합니다. 을 사용 yield하면 처음 5 개 항목 만 반복합니다.


14
사용 FullList.Where(IsItemInPartialList)은 게으른 것입니다. 단지 컴파일러에서 생성 한 커스텀 --- gunk --- 코드가 훨씬 적습니다. 작성 및 유지 관리 시간이 줄어 듭니다. (물론, 이것은 단지 예일뿐입니다)
sehe

4
그게 Linq 아닌가요? Linq가 표지에서 매우 비슷한 일을한다고 상상합니다.
Robert Harvey

1
예. Linq는 가능한 경우 지연된 실행 ( yield return)을 사용했습니다 .
Chad Schouggins

11
yield return 문이 실행되지 않으면 여전히 빈 컬렉션 결과가 표시되므로 null 참조 예외에 대해 걱정할 필요가 없습니다. 초콜렛 뿌리를 가진 수익률 반환은 굉장합니다.
면도기

127

반복자 블록의 이점은 느리게 작동한다는 것입니다. 따라서 다음과 같은 필터링 방법을 작성할 수 있습니다.

public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
                                   Func<T, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

한 번에 하나 이상의 항목을 버퍼링하지 않고 원하는만큼 스트림을 필터링 할 수 있습니다. 예를 들어 반환 된 시퀀스의 첫 번째 값만 필요한 경우 모든 것을 새 목록에 복사하려는 이유는 무엇입니까?

다른 예로 반복자 블록을 사용하여 무한 스트림을 쉽게 만들 수 있습니다 . 예를 들어, 다음은 임의의 숫자 순서입니다.

public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
    Random rng = new Random();
    while (true)
    {
        yield return rng.Next(minInclusive, maxExclusive);
    }
}

목록에 무한 시퀀스를 어떻게 저장 하시겠습니까?

Edulinq 블로그 시리즈 는 반복자 블록을 많이 사용 하는 LINQ to Objects의 샘플 구현을 제공합니다 . LINQ는 근본적으로 게으르다. 목록에 넣는 것은 그렇게 작동하지 않는다.


1
당신을 좋아할지 아닌지 잘 모르겠습니다 RandomSequence. 나에게 IEnumerable은 foreach로 반복 할 수 있다는 것을 의미합니다. 그러나 이것은 분명히 무한 루프로 이어질 것입니다. IEnumerable 개념을 YMMV로 잘못 사용한다고 생각합니다.
Sebastian Negraszus

5
@SebastianNegraszus : 임의의 숫자 시퀀스는 논리적으로 무한합니다. IEnumerable<BigInteger>예를 들어 피보나치 시퀀스를 쉽게 표현할 수 있습니다 . foreach그것과 함께 사용할 수는 있지만 IEnumerable<T>그것이 유한하다는 보장은 없습니다 .
Jon Skeet

42

"목록"코드를 사용하면 다음 단계로 넘어 가기 전에 전체 목록을 처리해야합니다. "수율"버전은 처리 된 항목을 즉시 다음 단계로 전달합니다. "다음 단계"에 ".Take (10)"이 포함 된 경우 "수율"버전은 처음 10 개 항목 만 처리하고 나머지는 잊어 버립니다. "목록"코드는 모든 것을 처리했을 것입니다.

이는 많은 처리가 필요하거나 처리해야 할 항목이 많을 때 가장 큰 차이가 있음을 의미합니다.


23

yield목록에없는 항목을 반환 하는 데 사용할 수 있습니다 . 다음은 취소 될 때까지 목록을 무한 반복 할 수있는 작은 샘플입니다.

public IEnumerable<int> GetNextNumber()
{
    while (true)
    {
        for (int i = 0; i < 10; i++)
        {
            yield return i;
        }
    }
}

public bool Canceled { get; set; }

public void StartCounting()
{
    foreach (var number in GetNextNumber())
    {
        if (this.Canceled) break;
        Console.WriteLine(number);
    }
}

이것은 쓴다

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4

...기타. 취소 될 때까지 콘솔에


10
object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

위의 코드를 사용하여 FilteredList ()를 반복하고 item.Name == "James"가 목록의 두 번째 항목에 만족되면 사용하는 메소드 yield가 두 번 생성됩니다. 이것은 게으른 동작입니다.

list를 사용하는 메소드는 n 개의 객체를 모두 목록에 추가하고 전체 목록을 호출 메소드에 전달합니다.

이것은 IEnumerable과 IList의 차이점을 강조 할 수있는 유스 케이스입니다.


7

내가 사용한 실제 사례 yield는 피보나치 시퀀스를 계산하는 것입니다.

다음 코드를 고려하십시오.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
        Console.ReadKey();
    }

    private static IEnumerable<long> Fibonacci()
    {
        long a = 0;
        long b = 1;

        while (true)
        {
            long temp = a;
            a = b;

            yield return a;

            b = temp + b;
        }
    }
}

이것은 다음을 반환합니다 :

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005

Linq 확장을 사용하고 필요한 것만 쿼리 할 수있는 기능을 제공하여 무한 시리즈를 빠르고 쉽게 계산할 수 있기 때문에 좋습니다.


5
피보나치 수열 계산에서 "실제 세계"를 볼 수 없습니다.
Nek

나는 이것이 실제로 "실제"가 아니라는 것에 동의한다.
Casey

1

왜 [수율]을 사용합니까? 코드가 약간 적을뿐 아니라 어떻게해야합니까?

때로는 유용하지만 때로는 그렇지 않습니다. 전체 데이터 집합을 검사하고 반환해야하는 경우 오버 헤드가 발생했기 때문에 yield를 사용하면 아무런 이점이 없습니다.

yield가 실제로 빛날 때 부분 집합 만 반환됩니다. 가장 좋은 예는 정렬이라고 생각합니다. 올해의 날짜와 달러 금액이 포함 된 객체 목록이 있고 해당 연도의 첫 소수 레코드를보고 싶다고 가정합니다.

이 작업을 수행하려면 목록을 날짜별로 오름차순으로 정렬 한 다음 처음 5 개를 가져와야합니다. 이 작업이 수율없이 수행 된 경우 마지막 두 날짜가 올바른지 확인하기 위해 전체 목록을 정렬해야합니다.

그러나 수율로 처음 5 개 품목이 설정되면 분류가 중지되고 결과가 제공됩니다. 이것은 많은 시간을 절약 할 수 있습니다.


0

yield return 문을 사용하면 한 번에 하나의 항목 만 반환 할 수 있습니다. 목록의 모든 항목을 수집하고 해당 목록을 다시 반환합니다. 이는 메모리 오버 헤드입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.