В вопросе Как я могу открыть только фрагмент IList <> один из ответов содержал следующий фрагмент кода:
IEnumerable<object> FilteredList()
{
foreach(object item in FullList)
{
if (IsItemInPartialList(item))
yield return item;
}
}
Что здесь делает ключевое слово yield? Я видел, как на него ссылаются в паре мест и еще один вопрос, но я не совсем понял, что он на самом деле делает. Я привык думать о yield в том смысле, что один поток уступает другому, но здесь это не кажется уместным.
Это не удивительно. Путаница возникает из-за того, что мы привыкли видеть «return» как выход функции, хотя перед ним стоит «yield», но это не так.
Я читал документы, но боюсь, что до сих пор не понимаю :(





Итерация. Он создает конечный автомат «под покровом», который запоминает, где вы были в каждом дополнительном цикле функции, и продолжает с этого момента.
Это очень простой и легкий способ создать перечислимый объект для вашего объекта. Компилятор создает класс, который обертывает ваш метод и в данном случае реализует IEnumerable <object>. Без ключевого слова yield вам пришлось бы создать объект, реализующий IEnumerable <object>.
Он производит перечислимую последовательность. Фактически он создает локальную последовательность IEnumerable и возвращает ее как результат метода.
Интуитивно понятно, что ключевое слово возвращает значение из функции, не покидая его, т.е. в вашем примере кода оно возвращает текущее значение item, а затем возобновляет цикл. Более формально он используется компилятором для генерации кода для итератор. Итераторы - это функции, возвращающие объекты IEnumerable. MSDN имеет несколько статьи.
Ну, если быть точным, он не возобновляет цикл, он приостанавливает его, пока родитель не вызовет "iterator.next ()".
@jitbit Вот почему я использовал «интуитивно» и «более формально».
Ключевое слово 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 заключается в том, что это очень быстрый способ реализации шаблона итератора, поэтому результаты оцениваются лениво.
Также стоит отметить, что вы можете использовать yield break;, если вы больше не хотите возвращать товары.
Любые операции Linq, используемые U, на самом деле являются отложенной загрузкой, которая выполняется с использованием yield, на самом деле все ORM использует это ...
yield - это не ключевое слово. Если бы это было, то я не мог бы использовать yield в качестве идентификатора, как в int yield = 500;.
1 ... 2 ... 4 ... 8 ... 16 ... 983475398745394875?
@Brandin, потому что все языки программирования поддерживают два типа ключевых слов: зарезервированные и контекстные. yield попадает в более позднюю категорию, поэтому ваш код не запрещен компилятором C#. Более подробная информация здесь: ericlippert.com/2009/05/11/reserved-and-contextual-keywords Вы были бы рады узнать, что есть также зарезервированные слова, которые не распознаются языком как ключевые слова. Например, goto в java. Подробнее здесь: stackoverflow.com/questions/2545103/…
@RBT Может быть, вы правы насчет C#. Но в других языках обычно нет необходимости различать ключевые слова и зарезервированные слова. Например, в Java вы можете рассматривать goto как ключевое слово, которое ничего не делает. Контекстные ключевые слова кажутся хорошей идеей для C#. Подход C++, например, состоял в том, чтобы попытаться переработать существующие ключевые слова, такие как delete и auto, чтобы дать им новые языковые функции.
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'. мне не кажется правильным. Я всегда думал о ключевом слове yield в C# в контексте «урожай дает обильный урожай», а не «автомобиль уступает дорогу пешеходу».
Зак, в этом случае return - это поставщик, а yield - маркер того, где поток управления был приостановлен, а само управление было отказано. IEnumerable передает управление вызывающему контексту / циклу, например, вашей машине и пешеходу, в то время как return несет полезную нагрузку текущего состояния, урожай в вашем примере. Да, только return в значительной степени отказывается от управления, но yield подобен goto, который создается в точке возврата, за которым следует следующий вызов; возобновление там, где последний уступил контроль, по сравнению с отказом от него в случае традиционного возврата.
Ключевое слово yield противоречит интуиции и не рекомендуется.
@pongapundit Или, может быть, вам нужно немного потренировать интуицию. Многие продвинутые функции языков программирования поначалу кажутся нелогичными.
Недавно Раймонд Чен также опубликовал интересную серию статей о ключевом слове yield.
Хотя он номинально используется для простой реализации шаблона итератора, но может быть обобщен в конечный автомат. Нет смысла цитировать Раймонда, последняя часть также ссылается на другие применения (но пример в блоге Энтина особенно хорош, показывая, как писать безопасный асинхронный код).
Это нужно проголосовать. Приятно, как он объясняет назначение оператора и внутренностей.
Часть 1 объясняет синтаксический сахар «доходности и возврата». отличное объяснение!
Он пытается привнести немного 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. А вот Это не значит, что это было бы хорошей идеей..
Еще лучше: общедоступный IEnumerable <int> Each () {int index = 0; yield return data [index ++]; }
Проще говоря, ключевое слово yield в C# допускает множество вызовов основной части кода, называемой итератором, который знает, как вернуться, прежде чем это будет выполнено, и при повторном вызове продолжает с того места, где было остановлено, то есть помогает итератору становятся прозрачно с сохранением состояния для каждого элемента в последовательности, которую итератор возвращает в последовательных вызовах.
В JavaScript та же концепция называется генераторами.
Лучшее объяснение. Это такие же генераторы в Python?
У Yield есть два важных применения:
Это помогает обеспечить настраиваемую итерацию без создания временных коллекций.
Чтобы более наглядно объяснить два вышеупомянутых пункта, я создал простое видео, которое вы можете посмотреть здесь
Видео помогло мне четко понять yield. Статья Какая польза от C# Yield? проекта кода @ ShivprasadKoirala с тем же объяснением также является хорошим источником
Я бы также добавил в качестве третьего пункта, что yield - это «быстрый» способ создания настраиваемого IEnumerator (вместо того, чтобы иметь класс, реализующий интерфейс IEnumerator).
Я смотрел ваше видео «Шивпрасад», и в нем четко объясняется использование ключевого слова yield.
Спасибо за видео !. Очень хорошо объяснено!
Отличное видео, но интересно ... Реализация с использованием yield явно чище, но она должна по существу создавать свою собственную временную память или / и список внутри, чтобы отслеживать состояние (или, скорее, создавать конечный автомат). Итак, делает ли Yield что-нибудь еще, кроме упрощения реализации и улучшения внешнего вида, или в этом есть что-то еще? Как насчет эффективности, работает ли код с использованием Yield более или менее эффективно / быстро, чем без него?
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»
На первый взгляд, возврат дохода - это сахар .СЕТЬ, чтобы вернуть 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 абсолютно +2
У этого связь есть простой пример
Еще более простые примеры здесь
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>(); }
Похоже, очень хорошее объяснение! Это мог быть принятый ответ.
@pongapundit, спасибо, мой ответ, безусловно, ясен и прост, но я сам мало использовал yield, у других респондентов гораздо больше опыта и знаний о его использовании, чем у меня. То, что я написал здесь о yield, вероятно, было результатом того, что я почесал голову, пытаясь выяснить некоторые ответы здесь и по этой ссылке dotnetperls! Но поскольку я не очень хорошо знаком с yield return (кроме той простой вещи, о которой я упомянул), мало использовал его и не знаю, как его использовать, я не думаю, что это должно быть принято.
Вот простой способ понять концепцию:
Основная идея заключается в том, что если вам нужна коллекция, в которой вы можете использовать «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();
}
Пытаясь распутать это, я создал диаграмму последовательности с удаленными абстракциями:
Конечный автомат, сгенерированный компилятором, также реализует перечислитель, но, чтобы сделать диаграмму более понятной, я показал их как отдельные экземпляры. (Когда конечный автомат перечисляется из другого потока, вы фактически получаете отдельные экземпляры, но эта деталь здесь не важна.)
Каждый раз, когда вы вызываете свой блок итератора, создается новый экземпляр конечного автомата. Однако ни один из ваших кодов в блоке итератора не выполняется до первого выполнения 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.
простой и блестящий
Одним из основных моментов ключевого слова 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 и т. д.
Просто ссылка MSDN об этом здесь msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx