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

이 코드가 주어지면 :

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



답변

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

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


답변

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

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는 근본적으로 게으르다. 목록에 넣는 것은 그렇게 작동하지 않는다.


답변

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

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


답변

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

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


답변

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의 차이점을 강조 할 수있는 유스 케이스입니다.


답변

내가 사용한 실제 사례 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 확장을 사용하고 필요한 것만 쿼리 할 수있는 기능을 제공하여 무한 시리즈를 빠르고 쉽게 계산할 수 있기 때문에 좋습니다.


답변

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

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

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

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

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