Добавленная стоимость ключевого слова доходности?

все еще пытаюсь найти, где я мог бы использовать ключевое слово "yield" в реальной ситуации.

Я вижу эту ветку на эту тему

Для чего используется ключевое слово yield в C #?

но в принятом ответе у них есть это как пример, когда кто-то повторяет Integer ()

public IEnumerable<int> Integers()
{
yield return 1;
yield return 2;
yield return 4;
yield return 8;
yield return 16;
yield return 16777216;
}

но почему бы просто не использовать

list<int>

здесь вместо этого. кажется более простым ..


person leora    schedule 21.12.2008    source источник
comment
возможный дубликат Каковы реальные приложения yield?   -  person nawfal    schedule 08.07.2014
comment
Ответ можно найти здесь: stackoverflow.com/questions/14057788/   -  person hdoghmen    schedule 17.06.2015


Ответы (10)


Если вы создадите и вернете список (скажем, в нем 1 миллион элементов), это большой кусок памяти, а также работы по его созданию.

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

Вот почему имеет смысл использовать yield return. Это не сильно отличается от создания всего списка и его возврата, но это совсем другое, потому что весь список не нужно создавать в памяти, прежде чем вызывающий сможет просмотреть первый элемент в нем.

Когда звонящий говорит:

foreach (int i in Integers())
{
   // do something with i
}

Каждый раз, когда циклу требуется новый i, он выполняет еще немного кода в Integer (). Код в этой функции «приостанавливается», когда встречается с оператором yield return.

person Daniel Earwicker    schedule 21.12.2008
comment
У меня были проблемы с пониманием уступки. Но ваш ответ был хорош! Я думаю, что использование yield более или менее похоже на разницу между использованием DataReader и DataSets. С помощью DataSets мы получили все данные, затем мы их обрабатываем, а с помощью DataReaders вы можете работать с данными, пока они поступают из источника. :-) - person Click Ok; 24.06.2009

Yield позволяет создавать методы, производящие данные, без необходимости собирать все данные перед возвратом. Думайте об этом как о возвращении нескольких значений по пути.

Вот пара методов, которые иллюстрируют суть

public IEnumerable<String> LinesFromFile(String fileName)
{
    using (StreamReader reader = new StreamReader(fileName))
    {
        String line;
        while ((line = reader.ReadLine()) != null)
            yield return line;
    }
}

public IEnumerable<String> LinesWithEmails(IEnumerable<String> lines)
{
    foreach (String line in lines)
    {
        if (line.Contains("@"))
            yield return line;
    }
}

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

foreach (String lineWithEmail in LinesWithEmails(LinesFromFile("test.txt")))
    Console.Out.WriteLine(lineWithEmail);
person Lasse V. Karlsen    schedule 21.12.2008

Вы можете использовать yield для создания любого итератора. Это может быть серия с ленивой оценкой (например, чтение строк из файла или базы данных без чтения всего сразу, что может быть слишком много для хранения в памяти) или может быть повторение существующих данных, таких как List<T>.

В C # in Depth есть бесплатная глава (6) все о блоках итераторов.

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

Для примера ленивого чтения файлов:

    static IEnumerable<string> ReadLines(string path) {
        using (StreamReader reader = File.OpenText(path)) {
            string line;
            while ((line = reader.ReadLine()) != null) {
                yield return line;
            }
        }
    }

Это полностью «лениво»; ничего не читается, пока вы не начнете перечисление, и только одна строка всегда сохраняется в памяти.

Обратите внимание, что LINQ-to-Objects широко использует блоки итератора (yield). Например, расширение Where по сути:

   static IEnumerable<T> Where<T>(this IEnumerable<T> data, Func<T, bool> predicate) {
        foreach (T item in data) {
            if (predicate(item)) yield return item;
        }
    }

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

person Marc Gravell    schedule 21.12.2008
comment
Хорошая статья о ленивом брутфорсе, как насчет того, чтобы вы ожидали список с одним единственным объектом, вы бы использовали Single (), чтобы гарантировать это? это хорошая практика? - person CloudyMarble; 21.05.2013
comment
@CloudyMarble, конечно, это вполне разумный способ убедиться, что он прошел проверку. Обратите внимание, что First() может быть дешевле - позволяет избежать поиска второго элемента - поэтому все зависит от того, хотите ли вы утверждать хотя бы одно против ровно одного - person Marc Gravell; 21.05.2013

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

person spender    schedule 21.12.2008

Реальная ситуация для меня - это когда я хочу обработать коллекцию, которая требует времени для более плавного заполнения.

Представьте себе что-нибудь вроде строк (псевдо-код):

public IEnumberable<VerboseUserInfo> GetAllUsers()
{
    foreach(UserId in userLookupList)
    {
        VerboseUserInfo info = new VerboseUserInfo();

        info.Load(ActiveDirectory.GetLotsOfUserData(UserId));
        info.Load(WebSerice.GetSomeMoreInfo(UserId));

        yield return info;
    }
}

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

person user48112    schedule 21.12.2008

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

Но есть преимущества в использовании yield.

  • Yield предоставляет простой способ создания итераторов с отложенным вычислением. (Это означает, что при вызове метода MoveNext () выполняется только код для получения следующего элемента в последовательности, после чего итератор не выполняет больше вычислений, пока метод не будет вызван снова)

  • Yield строит под крышками конечный автомат, и это избавляет вас от лишней работы, поскольку вам не нужно кодировать состояния вашего универсального генератора => более лаконичный / простой код.

  • Yield автоматически создает оптимизированные и потокобезопасные итераторы, избавляя вас от подробностей о том, как их создавать.

  • Yield намного эффективнее, чем кажется на первый взгляд, и может использоваться не только для создания простых итераторов. Посмотрите это видео, чтобы увидеть Джеффри Рихтер и его AsyncEnumerator и то, как используется yield, упрощают кодирование с использованием шаблона async.

person Pop Catalin    schedule 21.12.2008

Возможно, вы захотите перебрать различные коллекции:

public IEnumerable<ICustomer> Customers()
{
        foreach( ICustomer customer in m_maleCustomers )
        {
            yield return customer;
        }

        foreach( ICustomer customer in m_femaleCustomers )
        {
            yield return customer;
        }

        // or add some constraints...
        foreach( ICustomer customer in m_customers )
        {
            if( customer.Age < 16 )
            {
                yield return customer;
            }
        }

        // Or....            
        if( Date.Today == 1 )
        {
            yield return m_superCustomer;
        }

}
person Trap    schedule 21.12.2008
comment
Если вам интересно (и не известно о Linq), вы можете написать все это как: return m_maleCustomers.Concat (m_femaleCustomers) .Concat (m_customers.Where (c = ›c.Age‹ 16)). Concat (Enumerable.Repeat (m_superCustomer, 1). Где (Date.Today == 1); - person Daniel Earwicker; 21.12.2008

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

IEnumerable<double> GetListFromFile(int idxItem)
{
    // read data from file
    return dataReadFromFile;
}

IEnumerable<double> ConvertUnits(IEnumerable<double> items)
{
    foreach(double item in items)
        yield return convertUnits(item);
}

IEnumerable<double> DoExpensiveProcessing(IEnumerable<double> items)
{
    foreach(double item in items)
        yield return expensiveProcessing(item);
}

IEnumerable<double> GetNextList()
{
    return DoExpensiveProcessing(ConvertUnits(GetListFromFile(curIdx++)));
}

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

person Jon Norton    schedule 21.12.2008

Я придумал это, чтобы преодолеть недостаток .net, когда нужно вручную глубоко копировать List.

Я использую это:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

И в другом месте:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

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

РЕДАКТИРОВАТЬ:

Еще лучше использовать общий клонер списка:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}
person Daniel Mošmondor    schedule 30.09.2009

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

Вот эти навороты:

static IEnumerable<string> GetItems()
{
    yield return "apple";
    yield return "orange";
    yield return "pear";
}

foreach(string item in GetItems())
{
    Console.WriteLine(item);
}

в основном эквивалентно старомодному:

interface ItemProcessor
{
    void ProcessItem(string s);
};

class MyItemProcessor : ItemProcessor
{
    public void ProcessItem(string s)
    {
        Console.WriteLine(s);
    }
};

static void ProcessItems(ItemProcessor processor)
{
    processor.ProcessItem("apple");
    processor.ProcessItem("orange");
    processor.ProcessItem("pear");
}

ProcessItems(new MyItemProcessor());
person Matthew    schedule 17.02.2010
comment
На самом деле это не эквивалентно, поскольку каждый подход допускает то, что невозможно сделать в другом. Например, можно параллельно выполнять итерацию двух IEnumerable для чего-то вроде операции слияния; такое было бы невозможно с подходом передачи делегатов. С другой стороны, метод в стиле DoForEach, такой как ProcessItems, мог бы принимать параметр по ссылке и передавать его по ссылке вложенному делегату; такие методы также заключают вызовы вложенных элементов в блоки try, и им не нужно беспокоиться о том, что они будут оставлены без удаления. - person supercat; 04.08.2011