Правильное использование термина "доходность"

StackOverflow https://stackoverflow.com/questions/410026

  •  03-07-2019
  •  | 
  •  

Вопрос

Тот Самый уступать ключевое слово - одно из таких ключевые слова в C # это продолжает озадачивать меня, и я никогда не был уверен, что использую это правильно.

Какой из следующих двух фрагментов кода является предпочтительным и почему?

Версия 1: Использование yield возврат

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

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

Другой случай, когда 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 ключевое слово в C #, прочитав презентацию о генераторах в Python:Дэвид М.У Бизли http://www.dabeaz.com/generators/Generators.pdf.Вам не нужно много знать Python, чтобы понять презентацию - я этого не делал.Я нашел это очень полезным в объяснении не только того, как работают генераторы, но и того, почему вас это должно волновать.

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

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

Используя ключевое слово yield (наряду с словом Роба Айзенбурга Калибровка.Микропрограммы реализация) позволяет мне выразить асинхронный вызов веб-службы следующим образом:

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 и завершенное событие.Caliburn.Micro извлекает IEnumerator из вызова HandleButtonClick() и передает его в сопрограмму.Запустите метод Execute.Метод BeginExecute запускает итерацию по IResults.Когда возвращается первый IResult, выполнение приостанавливается внутри HandleButtonClick(), а BeginExecute() присоединяет обработчик события к завершенному событию и вызывает 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.

Возврат Yield может быть очень эффективным для алгоритмов, где вам нужно перебирать миллионы объектов.Рассмотрим следующий пример, в котором вам нужно рассчитать возможные поездки для 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 мс без какого-либо потребления памяти вообще.

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

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

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

Урожайность имеет два больших применения

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

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

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

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

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

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

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

Напротив, код после первого возврата yield здесь может быть выполнен:

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

В этих случаях полезно помнить, что yield return не является “окончательным”, как return.

Предполагая, что ваш класс products LINQ использует аналогичный yield для перечисления / итерации, первая версия более эффективна, потому что она выдает только одно значение при каждом повторении.

Второй пример - преобразование перечислителя / итератора в список с помощью метода ToList().Это означает, что он вручную перебирает все элементы в перечислителе, а затем возвращает плоский список.

Это как бы не по делу, но поскольку вопрос помечен как best-practices, я продолжу и добавлю свои два цента.Для такого рода вещей я бы предпочел превратить их в собственность:

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

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

А как насчет этого?

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

Вот несколько примеров, когда можно было бы использовать yield return:

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

Чтобы ответить на ваши вопросы, я бы использовал версию 2.

Верните список напрямую.Преимущества:

  • Это более понятно
  • Этот список можно использовать повторно.(итератор не является) на самом деле это не так, Спасибо, Джон

Вы должны использовать итератор (yield), начиная с того момента, когда вы считаете, что вам, вероятно, не придется выполнять итерацию до конца списка, или когда у него нет конца.Например, вызывающий клиент будет искать первый продукт, который удовлетворяет некоторому предикату, вы могли бы рассмотреть возможность использования итератора, хотя это надуманный пример, и, вероятно, есть лучшие способы выполнить это.В принципе, если вы заранее знаете, что нужно будет рассчитать весь список, просто сделайте это заранее.Если вы думаете, что этого не произойдет, то рассмотрите возможность использования версии итератора.

Ключевая фраза yield return используется для поддержания конечного автомата для конкретной коллекции.Везде, где CLR видит, что используется ключевая фраза возврата yield, CLR реализует шаблон перечислителя для этого фрагмента кода.Этот тип реализации помогает разработчику избавиться от всех видов сантехники, которые нам в противном случае пришлось бы выполнять в отсутствие ключевого слова.

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

Подробнее о ключевое слово здесь, в этой статье.

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

уступать имеет два преимущества:

  1. Вам не нужно перечитывать эти значения дважды;
  2. Вы можете получить много дочерних узлов, но вам не обязательно помещать их все в память.

Есть еще один ясный объяснение может быть, это поможет тебе.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top