'수익률'의 적절한 사용


903

항복 키워드는 그 중 하나입니다 키워드 나 신비화 계속 C #에서, 나는 올바르게 사용하고 있음을 나는 확신 적이 없습니다.

다음 두 가지 코드 중 어떤 것이 선호되고 왜 그럴까요?

버전 1 : 수익률 사용

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        foreach (Product product in products)
        {
            yield return product;
        }
    }
}

버전 2 : 목록 반환

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList<Product>();
    }
}

38
yieldIEnumerable<T>그리고 그 종류에 묶여 있습니다. 그것은 어쨌든 게으른 평가에있다
Jaider

다음은 비슷한 질문에 대한 답입니다. stackoverflow.com/questions/15381708/…
Sanjeev Rai

1
다음은 좋은 사용 예입니다 : stackoverflow.com/questions/3392612/…
ValGe

6
yield return결과를 반복하는 코드가 GetAllProducts()사용자에게 처리를 조기에 취소 할 수있는 경우를 사용하는 것이 좋습니다 .
JMD

2
나는이 스레드가 정말 도움이 발견 programmers.stackexchange.com/a/97350/148944
PiotrWolkowski에게

답변:


806

목록의 다음 항목 (또는 다음 항목 그룹)을 계산할 때 수익률 반환을 사용하는 경향이 있습니다.

버전 2를 사용하면 리턴하기 전에 전체 목록이 있어야합니다. 수익률을 사용하면 반품하기 전에 다음 품목 만 있으면됩니다.

무엇보다도 복잡한 계산의 계산 비용을 더 큰 시간 범위에 걸쳐 분산시키는 데 도움이됩니다. 예를 들어, 목록이 GUI에 연결되어 있고 사용자가 마지막 페이지로 이동하지 않으면 목록의 최종 항목을 계산하지 않습니다.

yield-return이 바람직한 다른 경우는 IEnumerable이 무한 세트를 나타내는 경우입니다. 소수 목록 또는 무한 난수 목록을 고려하십시오. 한 번에 전체 IEnumerable을 반환 할 수 없으므로 yield-return을 사용하여 목록을 증분 반환합니다.

특정 예에서는 전체 제품 목록이 있으므로 버전 2를 사용합니다.


31
나는 질문 3의 예에서 두 가지 이점을 돋보이게한다는 것을 nitpick하고 싶습니다. 1) 계산 비용을 분산시킵니다 (때로는 이점이 아니라 때로는 이점이 없습니다) 2) 많은 사용 사례에서 계산을 무기한으로 피할 수 있습니다. 중간 상태를 유지한다는 잠재적 인 단점은 언급하지 못했습니다. 많은 양의 중간 상태 (예 : 중복 제거를위한 HashSet)가있는 경우 yield를 사용하면 메모리 공간이 늘어날 수 있습니다.
Kennet Belenky

8
또한 각 개별 요소가 매우 크지 만 순차적으로 액세스해야하는 경우 수율이 더 좋습니다.
Kennet Belenky

2
그리고 마지막으로 ... 매우 직렬화 된 형태로 비동기 코드를 작성하기 위해 yield를 사용하는 약간 기발하지만 때로는 효과적인 기술이 있습니다.
Kennet Belenky

12
큰 CSV 파일을 읽을 때 흥미로운 또 다른 예가 있습니다. 각 요소를 읽고 싶지만 종속성을 추출하고 싶습니다. IEnumerable <>을 반환하면 각 행을 반환하고 각 행을 개별적으로 처리 할 수 ​​있습니다. 10 Mb 파일을 메모리로 읽을 필요가 없습니다. 한 번에 한 줄만.
Maxime Rouiller

1
Yield return자신의 사용자 정의 반복자 클래스 (구현 IEnumerator)를 작성하는 것의 축약 형 인 것 같습니다. 따라서 언급 된 이점은 사용자 정의 반복자 클래스에도 적용됩니다. 어쨌든, 두 구조는 중간 상태를 유지합니다. 가장 간단한 형태는 현재 객체에 대한 참조를 유지하는 것입니다.
J. Ouwehand

641

임시 목록을 채우는 것은 전체 비디오를 다운로드하는 yield것과 같지만 사용하는 것은 해당 비디오를 스트리밍하는 것과 같습니다.


180
이 답변이 기술적 인 답변이 아니라는 것을 완벽하게 알고 있지만, 수익률 키워드를 이해할 때 수익률과 동영상 스트리밍의 유사점이 좋은 예라고 생각합니다. 이 주제에 대해 기술적 인 모든 것이 이미 언급되었으므로 "다른 말로"설명하려고했습니다. 기술적이지 않은 용어로 아이디어를 설명 할 수 없다는 커뮤니티 규칙이 있습니까?
anar khalilov

13
나는 누가 당신에게 투표를했는지 또는 왜 (나는 그들이 의견을 말하고 싶었는지) 확신하지 못하지만, 그것이 비 기술적 전망에서 다소 설명한다고 생각합니다.
senfo

22
여전히 개념을 파악하고 초점을 맞추는 데 도움이되었습니다.
Tony

11
나는이 답변을 좋아하지만 질문에 대답하지는 않습니다.
ANeves

73

를 사용해야 할 때를 이해하기위한 개념적인 예로서 yield, 메소드 ConsumeLoop()ProduceList()다음으로 리턴 / 수율 된 항목을 처리 한다고 가정 해 봅시다 .

void ConsumeLoop() {
    foreach (Consumable item in ProduceList())        // might have to wait here
        item.Consume();
}

IEnumerable<Consumable> ProduceList() {
    while (KeepProducing())
        yield return ProduceExpensiveConsumable();    // expensive
}

이 없으면 을 반환하기 전에 목록을 작성해야하므로 yield호출 ProduceList()시간이 오래 걸릴 수 있습니다.

//pseudo-assembly
Produce consumable[0]                   // expensive operation, e.g. disk I/O
Produce consumable[1]                   // waiting...
Produce consumable[2]                   // waiting...
Produce consumable[3]                   // completed the consumable list
Consume consumable[0]                   // start consuming
Consume consumable[1]
Consume consumable[2]
Consume consumable[3]

을 사용하면 yield"병렬로"정렬되어 재 ​​배열됩니다.

//pseudo-assembly
Produce consumable[0]
Consume consumable[0]                   // immediately Consume
Produce consumable[1]
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next

마지막으로 이미 많은 사람들이 이미 제안했듯이 이미 완성 된 목록이 있으므로 버전 2를 사용해야합니다.


30

나는 이것이 오래된 질문이라는 것을 알고 있지만, yield 키워드를 창조적으로 사용하는 방법의 한 예를 제시하고 싶습니다. 나는 이 기술로부터 정말로 혜택을 받았다. 이 질문에 걸려 넘어지는 다른 사람에게 도움이되기를 바랍니다.

참고 : yield 키워드는 컬렉션을 만드는 또 다른 방법이라고 생각하지 마십시오. 생산 능력의 큰 부분은 호출 코드가 다음 값을 반복 할 때까지 메소드 또는 속성에서 실행이 일시 중지 된다는 사실 에 있습니다. 내 예는 다음과 같습니다.

yield 키워드 (Rob Eisenburg의 Caliburn.Micro coroutines 구현 과 함께 )를 사용하면 다음과 같이 웹 서비스에 대한 비동기 호출을 표현할 수 있습니다.

public IEnumerable<IResult> HandleButtonClick() {
    yield return Show.Busy();

    var loginCall = new LoginResult(wsClient, Username, Password);
    yield return loginCall;
    this.IsLoggedIn = loginCall.Success;

    yield return Show.NotBusy();
}

이 작업은 BusyIndicator를 켜고 웹 서비스에서 Login 메서드를 호출하고 IsLoggedIn 플래그를 반환 값으로 설정 한 다음 BusyIndicator를 다시 끕니다.

작동 방식은 다음과 같습니다. IResult에는 Execute 메서드와 Completed 이벤트가 있습니다. Caliburn.Micro는 IEnumerator를 HandleButtonClick ()에 대한 호출에서 가져와 Coroutine.BeginExecute 메서드에 전달합니다. BeginExecute 메소드는 IResults를 통해 반복을 시작합니다. 첫 번째 IResult가 반환되면 HandleButtonClick () 내에서 실행이 일시 중지되고 BeginExecute ()는 이벤트 처리기를 Completed 이벤트에 연결하고 Execute ()를 호출합니다. IResult.Execute ()는 동기 또는 비동기 작업을 수행 할 수 있으며 완료되면 Completed 이벤트를 발생시킵니다.

LoginResult는 다음과 같습니다.

public LoginResult : IResult {
    // Constructor to set private members...

    public void Execute(ActionExecutionContext context) {
        wsClient.LoginCompleted += (sender, e) => {
            this.Success = e.Result;
            Completed(this, new ResultCompletionEventArgs());
        };
        wsClient.Login(username, password);
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
    public bool Success { get; private set; }
}

이와 같은 것을 설정하고 실행을 단계별로 진행하여 상황을 관찰하는 것이 도움이 될 수 있습니다.

이것이 누군가를 돕기를 바랍니다! 수확량을 사용할 수있는 다른 방법을 탐색하는 것을 정말 즐겼습니다.


1
코드 샘플은 for 또는 foreach 블록의 yield OUTSIDE를 사용하는 방법에 대한 훌륭한 예입니다. 대부분의 예제는 이터레이터 내에서 수익률 반환을 보여줍니다. SO에 대한 질문을 할 때 매우 유용합니다. 반복자 외부에서 yield를 사용하는 방법!
shelbypereira

yield이런 식 으로 사용 된 적이 없었 습니다. 비동기 / 대기 패턴을 에뮬레이트하는 우아한 방법 인 것 같습니다 (이것이 yield오늘 다시 쓰여지면 대신 사용될 것이라고 가정 합니다). yield이 질문에 대답 한 후 C #이 발전함에 따라 이러한 창의적 사용으로 인해 수년 동안 수익이 줄어들고 있음을 발견 했습니까 ? 아니면 여전히 이와 같은 현대적인 영리한 사용 사례를 제시하고 있습니까? 그렇다면 다른 흥미로운 시나리오를 공유 하시겠습니까?
Lopsided

27

이것은 기괴한 제안처럼 보이지만 yield파이썬에서 발전기에 대한 프레젠테이션을 읽음으로써 C # 에서 키워드 를 사용하는 방법을 배웠습니다 . David M. Beazley의 http://www.dabeaz.com/generators/Generators.pdf . 프레젠테이션을 이해하기 위해 많은 Python을 알 필요는 없습니다. 발전기의 작동 방식뿐만 아니라 왜 신경 써야 하는지를 설명하는 데 매우 도움이되었습니다.


1
프레젠테이션은 간단한 개요를 제공합니다. C #에서 작동하는 방법에 대한 자세한 내용은 Ray Chen이 stackoverflow.com/a/39507/939250 의 링크에서 설명합니다. 첫 번째 링크는 수율 반환 방법 끝에 두 번째 암시 적 반환이 있음을 자세히 설명합니다.
Donal Lafferty

18

수백만 개의 객체를 반복해야하는 알고리즘에 대해서는 수율 반환이 매우 강력 할 수 있습니다. 승차 공유를 위해 가능한 여행을 계산해야하는 다음 예를 고려하십시오. 먼저 가능한 여행을 생성합니다.

    static IEnumerable<Trip> CreatePossibleTrips()
    {
        for (int i = 0; i < 1000000; i++)
        {
            yield return new Trip
            {
                Id = i.ToString(),
                Driver = new Driver { Id = i.ToString() }
            };
        }
    }

그런 다음 각 여행을 반복하십시오.

    static void Main(string[] args)
    {
        foreach (var trip in CreatePossibleTrips())
        {
            // possible trip is actually calculated only at this point, because of yield
            if (IsTripGood(trip))
            {
                // match good trip
            }
        }
    }

yield 대신 List를 사용하는 경우 메모리에 백만 개의 객체를 할당해야하며 (~ 190mb)이 간단한 예제를 실행하려면 ~ 1400ms가 걸립니다. 그러나 yield를 사용하면 이러한 모든 임시 객체를 메모리에 넣을 필요가 없으며 알고리즘 속도가 훨씬 빨라집니다.이 예제는 메모리 소비없이 전혀 ~ 400ms 만 실행됩니다.


2
덮개 아래에서 수확량이란 무엇입니까? 나는 그것이 목록이라고 생각했을 것이므로 메모리 사용을 어떻게 향상시킬 것인가?

1
@rolls yield는 상태 머신을 내부적으로 구현하여 적용됩니다. 다음 은 구현에 대해 자세히 설명하는 3 개의 자세한 MSDN 블로그 게시물 이 포함 된 SO 답변입니다 . Raymond Chen @ MSFT
Shiva 작성

13

두 가지 코드는 실제로 두 가지 다른 일을하고 있습니다. 첫 번째 버전은 필요에 따라 멤버를 가져옵니다. 두 번째 버전은 작업을 시작 하기 전에 모든 결과를 메모리에로드 합니다.

이것에 대한 옳고 그른 대답은 없습니다. 어느 것이 바람직한지는 상황에 따라 다릅니다. 예를 들어, 쿼리를 완료해야하는 시간이 제한되어 있고 결과와 반 복합적인 작업을 수행해야하는 경우 두 번째 버전이 더 좋습니다. 그러나 특히이 코드를 32 비트 모드로 실행하는 경우 큰 결과 집합에주의하십시오. 이 방법을 수행 할 때 OutOfMemory 예외에 여러 번 물 렸습니다.

명심해야 할 핵심 사항은 다음과 같습니다. 차이점은 효율성입니다. 따라서 코드를 더 간단하게 만들고 프로파일 링 후에 만 ​​코드를 변경해야합니다.


11

수확량에는 두 가지 큰 용도가 있습니다

임시 컬렉션을 만들지 않고 사용자 지정 반복을 제공하는 데 도움이됩니다. (모든 데이터로드 및 반복)

스테이트 풀 반복을 수행하는 데 도움이됩니다. (스트리밍)

아래는 위의 두 가지 점을 지원하기 위해 전체 데모로 만든 간단한 비디오입니다.

http://www.youtube.com/watch?v=4fju3xcm21M


10

이것이 Chris SellsC # 프로그래밍 언어의 문장에 대해 말한 것입니다 .

수율 반환 후 코드를 실행할 수 있다는 점에서 수율 반환이 return과 동일하지 않은 경우가 있습니다. 예를 들어, 첫 번째 리턴 이후의 코드는 실행할 수 없습니다.

    int F() {
return 1;
return 2; // Can never be executed
}

대조적으로 여기에서 첫 번째 수익률 반환 후 코드를 실행할 수 있습니다.

IEnumerable<int> F() {
yield return 1;
yield return 2; // Can be executed
}

이것은 종종 if 문에서 나를 물었습니다.

IEnumerable<int> F() {
if(...) { yield return 1; } // I mean this to be the only
// thing returned
yield return 2; // Oops!
}

이 경우 수익률 수익률이 수익률과 같이 "최종"이 아님을 기억하는 것이 도움이됩니다.


모호함을 줄이려면 할 수 있는가, 할 수 있는가? 첫 번째 수익률이 반환되고 두 번째 수익률을 실행하지 못할 수 있습니까?
조노 크로포드

@JohnoCrawford 두 번째 yield 문은 IEnumerable의 두 번째 / 다음 값이 열거 된 경우에만 실행됩니다. 예를 들어 F().Any()첫 번째 결과 만 열거하려고 시도하면 반환되지 않습니다. 일반적으로, IEnumerable yield실제로 트리거되지 않을 수 있기 때문에 프로그램 상태 변경 에 의존해서는 안됩니다
Zac Faragher

8

제품 LINQ 클래스가 열거 / 반복에 비슷한 수율을 사용한다고 가정 할 때 첫 번째 버전은 반복 될 때마다 하나의 값만 생성하므로 더 효율적입니다.

두 번째 예는 ToList () 메서드를 사용하여 열거 자 / 반복자를 목록으로 변환하는 것입니다. 즉, 열거 자의 모든 항목을 수동으로 반복 한 다음 단순 목록을 반환합니다.


8

이것은 요점 외에도 다소 문제가 있지만 질문에 모범 사례가 표시되어 있으므로 계속 진행하여 2 센트를 넣습니다. 이 유형의 경우 속성으로 만들기를 선호합니다.

public static IEnumerable<Product> AllProducts
{
    get {
        using (AdventureWorksEntities db = new AdventureWorksEntities()) {
            var products = from product in db.Product
                           select product;

            return products;
        }
    }
}

물론 조금 더 보일러 플레이트이지만 이것을 사용하는 코드는 훨씬 깨끗해 보입니다.

prices = Whatever.AllProducts.Select (product => product.price);

vs

prices = Whatever.GetAllProducts().Select (product => product.price);

참고 : 작업을 수행하는 데 시간이 걸릴 수있는 방법에는이 작업을 수행하지 않습니다.


7

이건 어때?

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList();
    }
}

나는 이것이 훨씬 깨끗하다고 ​​생각합니다. 그래도 확인해야 할 VS2008이 없습니다. 어쨌든 Products가 IEnumerable을 구현하면 (foreach 문에서 사용되는 것처럼) 직접 반환합니다.


2
답변을 게시하는 대신 더 많은 정보를 포함하도록 OP를 수정하십시오.
Brian Rasmussen

글쎄, 당신은 OP가 정확히 무엇을 의미하는지 말해
주어야

Original Post, 나는 가정합니다. 게시물을 편집 할 수 없으므로이 방법이 사용 된 것 같습니다.
petr k.

5

이 경우 코드 2 버전을 사용했을 것입니다. 사용 가능한 전체 제품 목록이 있고이 메소드 호출의 "소비자"가 예상 한 것이므로 전체 정보를 호출자에게 다시 보내야합니다.

이 방법의 호출자가 한 번에 "하나의"정보를 요구하고 다음 정보의 소비가 온 디맨드 방식 인 경우, 수익률 반환을 사용하면 실행 명령이 호출자에게 반환되도록하는 것이 유리합니다. 정보 단위를 사용할 수 있습니다.

수율 반환을 사용할 수있는 몇 가지 예는 다음과 같습니다.

  1. 발신자가 한 번에 한 단계 씩 데이터를 기다리는 복잡한 단계별 계산
  2. GUI에서의 페이징-사용자가 마지막 페이지에 도달 할 수 없으며 현재 페이지에 서브 세트의 정보 만 공개해야합니다.

귀하의 질문에 대답하기 위해 버전 2를 사용했을 것입니다.


3

목록을 직접 반환하십시오. 혜택:

  • 더 명확 해
  • 이 목록은 재사용 할 수 있습니다. (반복자는 사실 이 아닙니다) 사실 이 아닙니다 . 감사합니다 Jon

목록의 끝까지 반복 할 필요가 없거나 끝이없는 반복자 (수율)를 사용해야합니다. 예를 들어, 클라이언트 호출은 일부 술어를 만족시키는 첫 번째 제품을 검색 할 것입니다. 이터레이터 사용을 고려할 수도 있습니다. 비록 이것이 좋은 예일 수도 있지만 더 좋은 방법이있을 것입니다. 기본적으로 전체 목록을 계산해야한다는 것을 미리 알고 있다면 미리하십시오. 그렇지 않다고 생각되면 반복기 버전 사용을 고려하십시오.


IEnumerator <T>가 아니라 IEnumerable <T>로 돌아 오는 것을 잊지 마십시오. GetEnumerator를 다시 호출 할 수 있습니다.
Jon Skeet

사전에 전체 목록을 계산해야한다고해도 수익률 반환을 사용하는 것이 유리할 수 있습니다. 컬렉션에 수십만 개의 항목이 포함 된 경우를 예로들 수 있습니다.
Val

1

yield return keyphrase는 특정 컬렉션에 대한 상태 시스템을 유지 관리하는 데 사용됩니다. CLR이 yield return keyphrase가 사용되는 곳이면 CLR은 해당 코드에 Enumerator 패턴을 구현합니다. 이 유형의 구현은 키워드가 없을 때해야 할 모든 유형의 배관에서 개발자를 도와줍니다.

개발자가 일부 콜렉션을 필터링하고 콜렉션을 반복 한 다음 새 콜렉션에서 해당 오브젝트를 추출한다고 가정하십시오. 이런 종류의 배관은 단조롭습니다.

에 대한 자세한 이 문서에서 여기 키워드 .


-4

yield 의 사용법은 키워드 return 과 비슷하지만 제너레이터 를 반환한다는 점만 다릅니다 . 그리고 제너레이터 객체는 한 번만 통과 합니다 .

수확량 에는 두 가지 이점이 있습니다.

  1. 이 값을 두 번 읽을 필요는 없습니다.
  2. 많은 자식 노드를 얻을 수 있지만 메모리에 모두 넣을 필요는 없습니다.

또 다른 명확한 설명이 도움이 될 수 있습니다.

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