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

В вопросе Как я могу открыть только фрагмент IList <> один из ответов содержал следующий фрагмент кода:

IEnumerable<object> FilteredList()
{
    foreach(object item in FullList)
    {
        if (IsItemInPartialList(item))
            yield return item;
    }
}

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

Просто ссылка MSDN об этом здесь msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx

DmitryBoyko 24.04.2013 22:44

Это не удивительно. Путаница возникает из-за того, что мы привыкли видеть «return» как выход функции, хотя перед ним стоит «yield», но это не так.

Larry 20.02.2014 15:37

Я читал документы, но боюсь, что до сих пор не понимаю :(

Ortund 26.07.2017 13:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
884
3
363 828
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

Итерация. Он создает конечный автомат «под покровом», который запоминает, где вы были в каждом дополнительном цикле функции, и продолжает с этого момента.

Это очень простой и легкий способ создать перечислимый объект для вашего объекта. Компилятор создает класс, который обертывает ваш метод и в данном случае реализует IEnumerable <object>. Без ключевого слова yield вам пришлось бы создать объект, реализующий IEnumerable <object>.

Он производит перечислимую последовательность. Фактически он создает локальную последовательность IEnumerable и возвращает ее как результат метода.

Интуитивно понятно, что ключевое слово возвращает значение из функции, не покидая его, т.е. в вашем примере кода оно возвращает текущее значение item, а затем возобновляет цикл. Более формально он используется компилятором для генерации кода для итератор. Итераторы - это функции, возвращающие объекты IEnumerable. MSDN имеет несколько статьи.

Ну, если быть точным, он не возобновляет цикл, он приостанавливает его, пока родитель не вызовет "iterator.next ()".

Alex 10.07.2013 16:15

@jitbit Вот почему я использовал «интуитивно» и «более формально».

Konrad Rudolph 10.07.2013 16:16
Ответ принят как подходящий

Ключевое слово yield на самом деле делает здесь довольно много.

Функция возвращает объект, реализующий интерфейс IEnumerable<object>. Если вызывающая функция запускает foreach для этого объекта, функция вызывается снова, пока она не «уступит». Это синтаксический сахар, введенный в C# 2.0. В более ранних версиях вам приходилось создавать собственные объекты IEnumerable и IEnumerator, чтобы делать подобные вещи.

Самый простой способ понять такой код - это ввести пример, установить несколько точек останова и посмотреть, что произойдет. Попробуйте выполнить этот пример:

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

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

Когда вы пройдете по примеру, вы обнаружите, что первый вызов Integers() возвращает 1. Второй вызов возвращает 2, и строка yield return 1 больше не выполняется.

Вот пример из реальной жизни:

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

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

Mendelt 22.12.2008 11:35

Также стоит отметить, что вы можете использовать yield break;, если вы больше не хотите возвращать товары.

Rory 17.05.2011 22:13

Любые операции Linq, используемые U, на самом деле являются отложенной загрузкой, которая выполняется с использованием yield, на самом деле все ORM использует это ...

Vishal Sharma 12.12.2013 10:13

yield - это не ключевое слово. Если бы это было, то я не мог бы использовать yield в качестве идентификатора, как в int yield = 500;.

Brandin 19.02.2015 22:15

1 ... 2 ... 4 ... 8 ... 16 ... 983475398745394875?

iliketocode 03.07.2015 23:28

@Brandin, потому что все языки программирования поддерживают два типа ключевых слов: зарезервированные и контекстные. yield попадает в более позднюю категорию, поэтому ваш код не запрещен компилятором C#. Более подробная информация здесь: ericlippert.com/2009/05/11/reserved-and-contextual-keywords Вы были бы рады узнать, что есть также зарезервированные слова, которые не распознаются языком как ключевые слова. Например, goto в java. Подробнее здесь: stackoverflow.com/questions/2545103/…

RBT 19.05.2016 03:27

@RBT Может быть, вы правы насчет C#. Но в других языках обычно нет необходимости различать ключевые слова и зарезервированные слова. Например, в Java вы можете рассматривать goto как ключевое слово, которое ничего не делает. Контекстные ключевые слова кажутся хорошей идеей для C#. Подход C++, например, состоял в том, чтобы попытаться переработать существующие ключевые слова, такие как delete и auto, чтобы дать им новые языковые функции.

Brandin 19.05.2016 10:41

'If a calling function starts foreach-ing over this object the function is called again until it "yields"'. мне не кажется правильным. Я всегда думал о ключевом слове yield в C# в контексте «урожай дает обильный урожай», а не «автомобиль уступает дорогу пешеходу».

Zack 31.05.2016 18:28

Зак, в этом случае return - это поставщик, а yield - маркер того, где поток управления был приостановлен, а само управление было отказано. IEnumerable передает управление вызывающему контексту / циклу, например, вашей машине и пешеходу, в то время как return несет полезную нагрузку текущего состояния, урожай в вашем примере. Да, только return в значительной степени отказывается от управления, но yield подобен goto, который создается в точке возврата, за которым следует следующий вызов; возобновление там, где последний уступил контроль, по сравнению с отказом от него в случае традиционного возврата.

schulmaster 12.07.2017 10:27

Ключевое слово yield противоречит интуиции и не рекомендуется.

pongapundit 11.07.2018 18:56

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

Mendelt 13.07.2018 12:05

Недавно Раймонд Чен также опубликовал интересную серию статей о ключевом слове yield.

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

Это нужно проголосовать. Приятно, как он объясняет назначение оператора и внутренностей.

sajidnizami 14.06.2011 14:45

Часть 1 объясняет синтаксический сахар «доходности и возврата». отличное объяснение!

Dror Weiss 11.02.2013 15:04

Он пытается привнести немного Ruby Goodness :)
Концепция: Это пример кода Ruby, который распечатывает каждый элемент массива

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

Реализация каждого метода массива дает передается вызывающей стороне («ставит x») с элементом каждый массива, аккуратно представленным как x. Затем вызывающий может делать с x все, что ему нужно.

Однако .Сеть здесь не идет полностью ... C#, похоже, объединил yield с IEnumerable, заставляя вас писать цикл foreach в вызывающей программе, как видно из ответа Мендельта. Немного менее элегантно.

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}

-1 Мне такой ответ не кажется правильным. Да, C# yield связан с IEnumerable, а в C# отсутствует концепция «блока» в Ruby. Но в C# есть лямбда-выражения, которые могут позволить реализовать метод ForEach, очень похожий на each в Ruby. А вот Это не значит, что это было бы хорошей идеей..

rsenna 15.05.2013 23:27

Еще лучше: общедоступный IEnumerable <int> Each () {int index = 0; yield return data [index ++]; }

ata 15.10.2014 18:19

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

В JavaScript та же концепция называется генераторами.

Лучшее объяснение. Это такие же генераторы в Python?

petrosmm 15.06.2018 16:14

У Yield есть два важных применения:

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

  2. Это помогает выполнять итерацию с отслеживанием состояния.

Чтобы более наглядно объяснить два вышеупомянутых пункта, я создал простое видео, которое вы можете посмотреть здесь

Видео помогло мне четко понять yield. Статья Какая польза от C# Yield? проекта кода @ ShivprasadKoirala с тем же объяснением также является хорошим источником

Dush 01.05.2015 00:46

Я бы также добавил в качестве третьего пункта, что yield - это «быстрый» способ создания настраиваемого IEnumerator (вместо того, чтобы иметь класс, реализующий интерфейс IEnumerator).

MrTourkos 07.12.2018 18:19

Я смотрел ваше видео «Шивпрасад», и в нем четко объясняется использование ключевого слова yield.

Tore Aurstad 05.03.2019 11:51

Спасибо за видео !. Очень хорошо объяснено!

Roblogic 25.11.2019 13:47

Отличное видео, но интересно ... Реализация с использованием yield явно чище, но она должна по существу создавать свою собственную временную память или / и список внутри, чтобы отслеживать состояние (или, скорее, создавать конечный автомат). Итак, делает ли Yield что-нибудь еще, кроме упрощения реализации и улучшения внешнего вида, или в этом есть что-то еще? Как насчет эффективности, работает ли код с использованием Yield более или менее эффективно / быстро, чем без него?

toughQuestions 19.03.2020 18:10

yield return используется с перечислителями. При каждом вызове оператора yield управление возвращается вызывающей стороне, но это гарантирует, что состояние вызываемого сохраняется. Из-за этого, когда вызывающий элемент перечисляет следующий элемент, он продолжает выполнение в методе вызываемого объекта from сразу после оператора yield.

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

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

Кроме того, состояние сохраняется для каждого перечисления. Предположим, у меня есть еще один вызов метода Fibs(), тогда состояние для него будет сброшено.

set prevFib = 1 - первое число Фибоначчи - это «1», а не «0»

fubo 03.03.2015 11:51

На первый взгляд, возврат дохода - это сахар .СЕТЬ, чтобы вернуть IEnumerable.

Без yield создаются сразу все элементы коллекции:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

Тот же код с использованием yield, он возвращает элемент за элементом:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

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

Оператор yield позволяет создавать элементы по мере необходимости. Это хорошая причина использовать его.

Реализация списка или массива немедленно загружает все элементы, тогда как реализация yield предоставляет решение отложенного выполнения.

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

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

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

Вот сравнение между сборкой первой коллекции, такой как список, и использованием yield.

Пример списка

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

Консольный вывод
ContactListStore: Создание контакта 1
ContactListStore: Создание контакта 2
ContactListStore: Создание контакта 3
Готовы перебрать коллекцию.

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

Пример доходности

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

Консольный вывод
Готовы перебрать коллекцию.

Примечание: сборка вообще не выполнялась. Это связано с характером "отложенного выполнения" IEnumerable. Создание предмета будет происходить только тогда, когда это действительно необходимо.

Давайте снова вызовем коллекцию и изменим поведение при получении первого контакта в коллекции.

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

Консольный вывод
Готовы пройтись по коллекции
ContactYieldStore: Создание контакта 1
Привет Боб

Отлично! Только первый контакт был установлен, когда клиент «вытащил» предмет из коллекции.

Этот ответ требует большего внимания! Спасибо

leon22 18.10.2019 08:49

@ leon22 абсолютно +2

snr 26.03.2020 07:19

У этого связь есть простой пример

Еще более простые примеры здесь

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

Обратите внимание, что метод yield return не возвращается из метода. Можно даже поставить WriteLine после yield return

Вышеупомянутое дает IEnumerable из 4 целых 4,4,4,4

Здесь с WriteLine. Добавит 4 в список, напечатает abc, затем добавит 4 в список, затем завершит метод и действительно вернется из метода (после завершения метода, как это произошло бы с процедурой без возврата). Но это будет иметь значение, список IEnumerable из int, который он возвращает по завершении.

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

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

Вы используете yield с типом возвращаемого значения метода IEnumerable. Если тип возвращаемого значения метода - int или List<int> и вы используете yield, он не будет компилироваться. Вы можете использовать возвращаемый тип метода IEnumerable без yield, но кажется, что вы не можете использовать yield без возвращаемого типа метода IEnumerable.

И чтобы заставить его выполнить, вы должны назвать его особым образом.

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}

примечание - если вы пытаетесь понять SelectMany, он использует yield, а также обобщенные типы .. этот пример может помочь public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; } и public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }

barlop 01.05.2016 05:16

Похоже, очень хорошее объяснение! Это мог быть принятый ответ.

pongapundit 11.07.2018 18:37

@pongapundit, спасибо, мой ответ, безусловно, ясен и прост, но я сам мало использовал yield, у других респондентов гораздо больше опыта и знаний о его использовании, чем у меня. То, что я написал здесь о yield, вероятно, было результатом того, что я почесал голову, пытаясь выяснить некоторые ответы здесь и по этой ссылке dotnetperls! Но поскольку я не очень хорошо знаком с yield return (кроме той простой вещи, о которой я упомянул), мало использовал его и не знаю, как его использовать, я не думаю, что это должно быть принято.

barlop 12.07.2018 16:09

Вот простой способ понять концепцию: Основная идея заключается в том, что если вам нужна коллекция, в которой вы можете использовать «foreach», но сбор элементов в коллекцию по какой-то причине является дорогостоящим (например, запрос их из базы данных), И вам часто не понадобится вся коллекция , затем вы создаете функцию, которая создает коллекцию по одному элементу за раз и возвращает ее потребителю (который затем может досрочно завершить сборку).

Подумайте об этом так: Вы идете к мясному прилавку и хотите купить фунт нарезанной ветчины. Мясник берет 10-фунтовую ветчину на спину, кладет ее на слайсер, нарезает целиком, затем приносит вам кучу ломтиков и отмеряет фунт. (СТАРЫЙ способ). С yield мясник подносит слайсер к прилавку и начинает нарезать и «сдавать» каждый ломтик на весы, пока он не достигнет 1 фунта, затем заворачивает его для вас, и все готово. Старый способ может быть лучше для мясника (позволяет ему организовать свое оборудование так, как ему нравится), но новый способ явно более эффективен в большинстве случаев для потребителя.

Ключевое слово yield позволяет вам создать IEnumerable<T> в форме на блок итератора. Этот блок итератора поддерживает отложенное исполнение, и если вы не знакомы с концепцией, он может показаться почти волшебным. Однако, в конце концов, это просто код, который выполняется без каких-либо странных уловок.

Блок итератора можно описать как синтаксический сахар, когда компилятор генерирует конечный автомат, который отслеживает, насколько далеко продвинулось перечисление перечисляемого. Чтобы перечислить перечислимое, вы часто используете цикл foreach. Однако петля foreach также является синтаксическим сахаром. Итак, вы - две абстракции, удаленные из реального кода, поэтому поначалу может быть трудно понять, как все это работает вместе.

Предположим, у вас есть очень простой блок итератора:

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

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

Для перечисления блока итератора используется цикл foreach:

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

Вот результат (здесь никаких сюрпризов):

Begin
1
After 1
2
After 2
42
End

Как указано выше, foreach - это синтаксический сахар:

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

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

C# iterator block sequence diagram

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

Каждый раз, когда вы вызываете свой блок итератора, создается новый экземпляр конечного автомата. Однако ни один из ваших кодов в блоке итератора не выполняется до первого выполнения enumerator.MoveNext(). Так работает отложенное выполнение. Вот (довольно глупый) пример:

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

На данный момент итератор не запущен. Предложение Where создает новый IEnumerable<T>, который является оболочкой для IEnumerable<T>, возвращаемого IteratorBlock, но это перечислимое число еще не было перечислено. Это происходит, когда вы выполняете цикл foreach:

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

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

Обратите внимание, что такие методы LINQ, как ToList(), ToArray(), First(), Count() и т. д., Будут использовать цикл foreach для перечисления перечисляемого. Например, ToList() перечислит все элементы перечисляемого и сохранит их в списке. Теперь вы можете получить доступ к списку, чтобы получить все элементы перечисляемого без повторного выполнения блока итератора. Существует компромисс между использованием ЦП для многократного создания элементов перечисления и памяти для хранения элементов перечисления для многократного доступа к ним при использовании таких методов, как ToList().

Если я правильно понимаю, вот как я бы сформулировал это с точки зрения функции, реализующей IEnumerable с yield.

  • Вот один.
  • Позвоните еще раз, если вам понадобится еще один.
  • Я запомню то, что я тебе уже дал.
  • Я узнаю, смогу ли я дать тебе еще один, когда ты снова позвонишь.

простой и блестящий

Harry 22.04.2020 13:10

Одним из основных моментов ключевого слова Yield является Ленивое выполнение. Под ленивым исполнением я подразумеваю выполнение при необходимости. Лучше выразиться, приведя пример

Пример: без использования Yield, т.е. без ленивого выполнения.

public static IEnumerable<int> CreateCollectionWithList()
{
    var list =  new List<int>();
    list.Add(10);
    list.Add(0);
    list.Add(1);
    list.Add(2);
    list.Add(20);

    return list;
}

Пример: использование Yield, т.е. Lazy Execution.

public static IEnumerable<int> CreateCollectionWithYield()
{
    yield return 10;
    for (int i = 0; i < 3; i++) 
    {
        yield return i;
    }

    yield return 20;
}

Теперь, когда я вызываю оба метода.

var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();

вы заметите, что listItems будет содержать 5 элементов (наведите указатель мыши на listItems во время отладки). В то время как yieldItems будет иметь ссылку только на метод, а не на элементы. Это означает, что он не выполнил процесс получения элементов внутри метода. Очень эффективный способ получения данных только при необходимости. Фактическую реализацию yield можно увидеть в ORM, например Entity Framework, NHibernate и т. д.

Другие вопросы по теме