Правильное использование «доходности» - PullRequest
842 голосов
/ 04 января 2009

Ключевое слово yield является одним из тех ключевых слов в 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>();
    }
}

Ответы [ 16 ]

762 голосов
/ 04 января 2009

Я склонен использовать доходность при расчете следующего элемента в списке (или даже следующей группы элементов).

Используя вашу версию 2, вы должны иметь полный список, прежде чем вернуться. При использовании функции возврата-возврата вам действительно нужно иметь только следующий товар перед возвратом.

Помимо прочего, это помогает распределить вычислительные затраты на сложные вычисления в течение более длительного периода времени. Например, если список подключен к графическому интерфейсу и пользователь никогда не переходит на последнюю страницу, вы никогда не рассчитываете конечные элементы в списке.

Другой случай, когда доходность является предпочтительным, - это если IEnumerable представляет бесконечное множество. Рассмотрим список простых чисел или бесконечный список случайных чисел. Вы никогда не сможете вернуть полный IEnumerable сразу, поэтому вы используете yield-return для постепенного возврата списка.

В вашем конкретном примере у вас есть полный список продуктов, поэтому я бы использовал версию 2.

593 голосов
/ 08 июля 2013

Заполнение временного списка похоже на загрузку всего видео, тогда как использование yield похоже на потоковую передачу этого видео.

65 голосов
/ 10 октября 2012

В качестве концептуального примера для понимания того, когда следует использовать 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, потому что у вас уже есть заполненный список.

26 голосов
/ 18 мая 2011

Я знаю, что это старый вопрос, но я хотел бы предложить один пример того, как можно творчески использовать ключевое слово yield. Я действительно извлек выгоду из этой техники. Надеюсь, это поможет всем, кто столкнется с этим вопросом.

Примечание. Не думайте, что ключевое слово yield является просто еще одним способом создания коллекции. Большая часть силы доходности заключается в том, что исполнение в вашем 100 * * паузе метод или свойство, пока вызывающий код не выполнит итерацию следующего значения. Вот мой пример:

Использование ключевого слова yield (наряду с реализацией 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, вызвать метод 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; }
}

Может помочь настроить что-то вроде этого и пройти через выполнение, чтобы посмотреть, что происходит.

Надеюсь, это кому-нибудь поможет! Мне очень понравилось исследовать различные способы использования урожайности.

26 голосов
/ 05 января 2009

Это может показаться странным предложением, но я узнал, как использовать ключевое слово yield в C #, прочитав презентацию о генераторах в Python: * 1003 Дэвида М. Бизли * Вам не нужно знать много Python, чтобы понять презентацию - я не знал. Мне было очень полезно объяснить не только, как работают генераторы, но и почему вы должны заботиться.

13 голосов
/ 22 ноября 2016

Возвращение доходности может быть очень мощным для алгоритмов, где вам нужно перебирать миллионы объектов. Рассмотрим следующий пример, где вам нужно рассчитать возможные поездки на rideshare. Сначала мы генерируем возможные поездки:

    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
            }
        }
    }

Если вы используете List вместо yield, вам потребуется выделить 1 миллион объектов в память (~ 190 МБ), и для этого простого примера потребуется ~ 1400 мс. Однако, если вы используете yield, вам не нужно помещать все эти временные объекты в память, и вы получите значительно более высокую скорость алгоритма: этот пример займет всего ~ 400 мс для запуска без использования памяти вообще.

12 голосов
/ 04 января 2009

Два куска кода действительно делают две разные вещи. Первая версия будет тянуть участников по мере необходимости. Вторая версия загрузит все результаты в память до того, как вы начнете с ней что-либо делать.

Там нет правильного или неправильного ответа на этот. Какой из них предпочтительнее, зависит только от ситуации. Например, если у вас есть ограничение по времени для выполнения запроса, и вам нужно сделать что-то сложное с результатами, вторая версия может быть предпочтительнее. Но остерегайтесь больших наборов результатов, особенно если вы запускаете этот код в 32-битном режиме. При выполнении этого метода меня несколько раз укусили исключения OutOfMemory.

Главное, что нужно иметь в виду, это то, что различия в эффективности. Таким образом, вам, вероятно, следует придерживаться того, что делает ваш код проще, и изменять его только после профилирования.

11 голосов
/ 05 апреля 2014

У доходности есть два замечательных применения

Это помогает обеспечить пользовательскую итерацию без создания временных коллекций. (загрузка всех данных и цикл)

Помогает выполнять итерацию с учетом состояния. (потоковое)

Ниже приведено простое видео, которое я создал с полной демонстрацией, чтобы поддержать два вышеупомянутых пункта

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

10 голосов
/ 27 мая 2015

Это то, что Крис Селлс рассказывает об этих утверждениях на языке программирования C # ;

Иногда я забываю, что доходность не совпадает с доходностью, в что код после возврата дохода может быть выполнен. Например, код после первого возврата здесь никогда не может быть выполнен:

    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!
}

В этих случаях запоминание того, что доходность не является «окончательным», как возвращение полезно.

8 голосов
/ 04 января 2009

Это даже не в этом дело, но, поскольку вопрос помечен как «лучшие практики», я добавлю два цента. Для этого типа вещей я предпочитаю превратить его в свойство:

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);

против

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

Примечание: Я бы не стал делать это для любых методов, которые могут занять некоторое время, чтобы выполнить свою работу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...