ProgramingTip

'수익률'의 적절한 사용

bestdevel 2020. 9. 28. 09:38
반응형

'수익률'의 적절한 사용


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

나는 목록의 다음 항목 (또는 다음 항목 그룹)을 계산할 때 yield-return을 사용하는 경향이 있습니다.

버전 2를 사용하는 경우 반환하기 전에 전체 목록이 필요합니다. yield-return을 사용하면 반환하기 전에 다음 항목 만 있으면됩니다.

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

yield-return이 바람직한 또 다른 경우는 IEnumerable이 무한 집합을위한 경우입니다. 소수 목록 또는 무한한 난수 목록을 고려하십시오. 한 번에 전체 IEnumerable을 반환 할 수있는 수율 반환을 사용하여 목록을 점진적으로 반환합니다.

특정 예에서 전체 제품 목록이 있으므로 버전 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를 사용합니다.


이 기괴한 제안처럼 보일 것입니다.하지만 저는 yieldPython에서 생성기에 대한 프레젠테이션을 읽음 C # 에서 키워드 를 사용하는 방법을 배웠습니다 : David M. Beazley의 http://www.dabeaz.com/generators/Generators. pdf . 프레젠테이션을 이해하기 위해 Python을 많이 알 필요는 없습니다. 저는 몰랐습니다. 제너레이터의 작동 방식을 설명하는 데 매우 유용합니다.


나는 이것이 오래된 질문이라는 것을 알고있다 키워드를 창의적으로 사용할 수있는 방법에 대한 한 가지 예를 제공하고 싶습니다. 나는 한 정말 이 기술의 혜택을. 바라건대이 질문을 우연히 발견 한 다른 누구에게 도움이 될 것입니다.

참고 : yield 키워드를 포함 컬렉션을 구축하는 또 다른 방법으로 생각하지에서. yield의 힘의 큰 부분은 호출 코드가 다음 값을 반복 할 때 중지 방법 또는 속성에서 실행 중지 되는 사실 에 있습니다. 내 예는 다음과 달라집니다.

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

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를 켜고 웹 서비스에서 로그인 메서드를 호출하고 IsLoggedIn 플래그를 반환 값으로 설정 한 다음 BusyIndicator를 다시 시작합니다.

작동 방식은 다음과 같습니다. IResult에는 Execute 메서드와 Completed 이벤트가 있습니다. Caliburn.Micro는 HandleButtonClick () 호출에서 IEnumerator를 가져 와서 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를 사용할 수있는 다양한 방법을 탐색하는 것을 정말 즐겼습니다.


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

    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(trips))
        {
            // possible trip is actually calculated only at this point, because of yield
            if (IsTripGood(trip))
            {
                // match good trip
            }
        }
    }

yield 대신 List를 사용하는 경우 1 백만 개의 개체를 메모리 (~ 190mb)에 할당해야하며이 간단한 예제를 실행하는 데 ~ 1400ms가 걸립니다. 그러나 yield를 사용하는 경우 이러한 모든 임시 개체를 메모리에 넣을 필요가 없으며 훨씬 더 빠른 알고리즘 속도를 얻을 수 있습니다.이 예제는 메모리 소비없이 실행하는 데 400ms 만 걸립니다.


두 코드는 실제로 두 가지 다른 작업을 수행합니다. 첫 번째 버전은 필요에 따라 구성원을 끌어옵니다. 두 번째 버전은 작업을 시작 하기 전에 모든 결과를 메모리에로드 합니다.

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

명심해야 할 핵심은 이것이다 : 차이점은 효율성에있다. 따라서 코드를 더 간단하게 만드는 방법을 사용하고 프로파일 링 후에 만 ​​변경해야합니다.


수익에는 두 가지 큰 용도가 있습니다.

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

상태 저장 반복을 수행하는 데 도움이됩니다. (스트리밍)

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

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


이것이 Chris SellsThe C # Programming Language의 이러한 명령문에 대해 말하는 것입니다 .

나는 때때로 yield return이 return과 같지 않다는 것을 잊는다. yield 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!
}

이러한 경우 수익률이 수익률처럼 "최종"이 아니라는 점을 기억하는 것이 도움이됩니다.


제품 LINQ 클래스가 열거 / 반복에 유사한 산출량을 사용한다고 가정하면 첫 번째 버전은 반복 될 때마다 하나의 값만 산출하므로 더 효율적입니다.

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


이것은 요점 외에도 다소 있지만 질문에 모범 사례 태그가 지정되어 있으므로 계속 진행하여 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);

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


그리고 이것은 어떻습니까?

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를 사용했을 것입니다. 사용 가능한 제품의 전체 목록이 있고 이것이이 메서드 호출의 "소비자"가 예상하는 것이므로 전체 정보를 호출자에게 다시 보내야합니다.

이 메서드의 호출자가 한 번에 "하나"의 정보를 필요로하고 다음 정보의 소비가 온 디맨드 기반 인 경우, 실행 명령이 호출자에게 반환되는지 확인하는 yield return을 사용하는 것이 좋습니다. 정보 단위를 사용할 수 있습니다.

수익률을 사용할 수있는 몇 가지 예는 다음과 같습니다.

  1. 호출자가 한 번에 한 단계의 데이터를 기다리는 복잡한 단계별 계산
  2. GUI에서 페이징-사용자가 마지막 페이지에 도달 할 수없고 현재 페이지에 정보의 하위 집합 만 공개하면되는 경우

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


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

  • 더 명확 해
  • 목록은 재사용이 가능합니다. (반복자가 아닙니다) 실제로 사실 이 아닙니다. Jon 감사합니다.

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


yield return keyphrase는 특정 컬렉션에 대한 상태 시스템을 유지하는 데 사용됩니다. CLR에서 사용중인 yield return keyphrase를 볼 때마다 CLR은 해당 코드 조각에 대해 열거 자 패턴을 구현합니다. 이러한 유형의 구현은 키워드가 없을 때 수행해야하는 모든 유형의 배관에서 개발자를 돕습니다.

개발자가 일부 컬렉션을 필터링하고 컬렉션을 반복 한 다음 일부 새 컬렉션에서 해당 개체를 추출한다고 가정합니다. 이런 종류의 배관은 아주 단조롭습니다.

이 기사에서 키워드 에 대해 자세히 알아보십시오 .


yield 의 사용법은 생성기 를 반환한다는 점을 제외 하면 키워드 return 과 유사합니다 . 그리고 발전기 객체는 통과합니다 .

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

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

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

참고 URL : https://stackoverflow.com/questions/410026/proper-use-of-yield-return

반응형