[C#] ‘수익률’의 적절한 사용

항복 키워드는 그 중 하나입니다 키워드 나 신비화 계속 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>();
    }
}



답변

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

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

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

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

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


답변

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


답변

를 사용해야 할 때를 이해하기위한 개념적인 예로서 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를 사용해야합니다.


답변

나는 이것이 오래된 질문이라는 것을 알고 있지만, 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; }
}

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

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


답변

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


답변

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

    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 만 실행됩니다.


답변

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

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

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