Какое ключевое слово yield используется для С#?
В вопросе Как я могу раскрыть только фрагмент IList<> у одного из ответов был следующий фрагмент кода:
IEnumerable<object> FilteredList()
{
foreach(object item in FullList)
{
if(IsItemInPartialList(item))
yield return item;
}
}
Что делает здесь ключевое слово yield? Я видел ссылки в нескольких местах, и еще один вопрос, но я не совсем понял, что он на самом деле делает. Я привык думать о доходности в том смысле, что один поток уступает другому, но здесь это не актуально.
Ответы
Ответ 1
Ключевое слово yield
самом деле очень много здесь делает.
Функция возвращает объект, который реализует интерфейс IEnumerable<object>
. Если вызывающая функция начинает foreach
по этому объекту, функция вызывается снова, пока она не "выдаст". Это синтаксический сахар, введенный в С# 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);
}
}
}
}
}
Ответ 2
итерация. Он создает конечный автомат "под крышками", который помнит, где вы были на каждом дополнительном цикле функции и выбирает оттуда.
Ответ 3
Выход имеет два больших использования,
Чтобы объяснить выше два пункта более демонстративно, я создал простое видео, которое вы можете посмотреть здесь
Ответ 4
Недавно Раймонд Чен также опубликовал интересную серию статей по ключевому слову yield.
Хотя он номинально используется для простой реализации шаблона итератора, но может быть обобщен в конечный автомат. Нет смысла цитировать Рэймонда, последняя часть также ссылается на другое использование (но пример в блоге Entin особенно хорош, показывая, как писать асинхронный безопасный код).
Ответ 5
На первый взгляд возвращаемая доходность - это сахар .NET, возвращающий IEnumerable.
Без выхода все элементы коллекции создаются сразу:
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 позволяет создавать элементы по мере необходимости. Это хорошая причина, чтобы использовать его.
Ответ 6
yield return
используется с счетчиками. При каждом вызове оператора yield управление возвращается вызывающему, но оно обеспечивает сохранение состояния вызываемого абонента. В связи с этим, когда вызывающий абонент перечисляет следующий элемент, он продолжает выполнение в методе callee из инструкции сразу после оператора 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()
, тогда для него будет reset.
Ответ 7
Интуитивно ключевое слово возвращает значение из функции, не оставляя его, то есть в вашем примере кода оно возвращает текущее значение item
, а затем возобновляет цикл. Более формально он используется компилятором для генерации кода для итератора. Итераторы - это функции, возвращающие объекты IEnumerable
. MSDN содержит несколько статей о них.
Ответ 8
Реализация списка или массива сразу же загружает все элементы, тогда как реализация yield предоставляет отложенное решение для выполнения.
На практике часто желательно выполнять минимальный объем работы по мере необходимости, чтобы уменьшить потребление ресурсов приложением.
Например, у нас может быть приложение, которое обрабатывает миллионы записей из базы данных. Следующие преимущества могут быть достигнуты, когда мы используем IEnumerable в отложенной модели pull-based исполнения:
- Масштабируемость, надежность и предсказуемость, вероятно, улучшатся, поскольку количество записей существенно не влияет на требования к ресурсам приложений.
- Эффективность и отзывчивость, вероятно, улучшатся, так как обработка может начаться немедленно, а не ждать загрузки первой коллекции.
- Возможность восстановления и использования, вероятно, улучшится, так как приложение может быть остановлено, запущено, прервано или не выполнено. Только те элементы, которые будут выполняться, будут потеряны по сравнению с предварительной выборкой всех данных, в которых фактически использовалась только часть результатов.
- Непрерывная обработка возможна в средах, где добавляются потоки постоянной рабочей нагрузки.
Вот сравнение между сборкой первой, такой как список, по сравнению с использованием 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
Привет, Bob
Ницца! Только первый контакт был создан, когда клиент "вытащил" элемент из коллекции.
Ответ 9
Вот простой способ понять концепцию:
Основная идея состоит в том, что если вы хотите использовать коллекцию, в которой вы можете использовать "foreach
", но сбор элементов в коллекции по какой-либо причине дорогостоящий (например, запрос из базы данных), и вам часто не понадобится целую коллекцию, то вы создаете функцию, которая собирает коллекцию по одному элементу за раз и возвращает ее обратно потребителю (кто может рано разорвать процесс сбора).
Подумайте об этом так: Вы идете на счетчик мяса и хотите купить фунт нарезанной ветчины. Мясник берет 10-фунтовую ветчину на спину, кладет ее на машину для резки, нарезает все это, затем возвращает кучу ломтиков вам и измеряет фунт. (OLD).
С помощью yield
мясник выводит машину сглаживателя на счетчик и начинает нарезать и "уронить" каждый срез на шкале до тех пор, пока он не будет измерять 1 фунт, а затем обертывает его для вас, и все готово. Старый путь может быть лучше для мясника (позволяет ему организовывать свою технику так, как ему нравится), но в большинстве случаев новый способ явно более эффективен для потребителя.
Ответ 10
Ключевое слово 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()
.
Ответ 11
Если я правильно понимаю, здесь, как я бы сформулировал это с точки зрения функции, реализующей IEnumerable с yield.
- Здесь один.
- Звоните еще раз, если вам нужен другой.
- Я буду помнить то, что я тебе уже дал.
- Я буду знать только, смогу ли я дать вам еще один, когда вы снова позвоните.
Ответ 12
Ключевое слово С# yield, просто говоря, допускает много вызовов к телу кода, называемому итератором, который знает, как вернуться до его выполнения и, когда вызывается снова, продолжается там, где он был остановлен, т.е. помогает итератору стать прозрачным с точки зрения состояния для каждого элемента в последовательности, в которой итератор возвращается при последовательных вызовах.
В JavaScript эта же концепция называется генераторами.
Ответ 13
Это простой и простой способ создания перечислимого для вашего объекта. Компилятор создает класс, который обертывает ваш метод и который реализует в этом случае IEnumerable <object> . Без ключевого слова yield вы должны создать объект, который реализует IEnumerable <object> .
Ответ 14
Он производит перечислимую последовательность. То, что он делает, фактически создает локальную последовательность IEnumerable и возвращает ее как результат метода
Ответ 15
Как только вы хорошо понимаете, как работают итераторные блоки, У Эрика Липперта отличная серия сообщений в блоге на некоторых, казалось бы, нечетных ограничения на общность блоков итератора.
Ответ 16
Эта ссылка имеет простой пример
Здесь даже более простые примеры
public static IEnumerable<int> testYieldb()
{
for(int i=0;i<3;i++) yield return 4;
}
Обратите внимание, что возврат результата не возвращается из метода. Вы даже можете поставить WriteLine
после yield return
Вышеизложенное генерирует IEnumerable из 4 целых чисел 4,4,4,4
Здесь с WriteLine
. Будет добавлен 4 в список, напечатайте abc, затем добавьте 4 в список, затем заполните метод и верните его обратно из метода (как только метод завершится, как это происходит с процедурой без возврата). Но это будет иметь значение, IEnumerable
список int
s, который он возвращает по завершении.
public static IEnumerable<int> testYieldb()
{
yield return 4;
console.WriteLine("abc");
yield return 4;
}
Обратите внимание также, что при использовании yield вы возвращаете не тот же тип, что и функция. Это тип элемента в списке IEnumerable
.
Вы используете yield с возвращаемым типом метода как IEnumerable
. Если тип возвращаемого метода - int
или List<int>
, и вы используете yield
, то он не будет компилироваться. Вы можете использовать тип возвращаемого метода IEnumerable
без урока, но, похоже, вы не можете использовать выход без IEnumerable
метода return type.
И чтобы заставить его выполнить, вы должны вызвать его особым образом.
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");
}
Ответ 17
Чтобы понять, что дает урожай, вы должны понять, что такое генераторы. И прежде чем вы сможете понять генераторы, вы должны понять итерируемые.
Итерации Когда вы создаете список, вы можете читать его элементы по одному. Чтение его элементов по одному называется итерацией:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
Mylist является повторяемым. Когда вы используете понимание списка, вы создаете список, и поэтому повторяемый:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
Все, что вы можете использовать "для... в...", является итеративным; списки, строки, файлы...
Эти итерации удобны, потому что вы можете читать их сколько угодно, но вы храните все значения в памяти, и это не всегда то, что вы хотите, когда у вас много значений.
Генераторы Генераторы - это итераторы, вид итераций, которые вы можете повторять только один раз. Генераторы не хранят все значения в памяти, они генерируют значения на лету:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
Это то же самое, за исключением того, что вы использовали() вместо []. НО, вы не можете выполнить для меня в mygenerator второй раз, так как генераторы могут использоваться только один раз: они вычисляют 0, затем забывают об этом и вычисляют 1, и заканчивают вычислять 4, один за другим.
Yield yield - это ключевое слово, которое используется как return, за исключением того, что функция возвращает генератор.
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
Здесь это бесполезный пример, но он полезен, когда вы знаете, что ваша функция вернет огромный набор значений, которые вам нужно будет прочитать только один раз.
Чтобы справиться с yield, вы должны понимать, что при вызове функции код, написанный в теле функции, не запускается. Функция возвращает только объект генератора, это немного сложно :-)
Затем ваш код будет продолжаться с того места, где он остановился каждый раз, когда он использует генератор.
Теперь самая сложная часть:
При первом вызове for вызывает объект генератора, созданный из вашей функции, он будет запускать код в вашей функции с самого начала, пока не достигнет yield, а затем вернет первое значение цикла. Затем каждый следующий вызов будет запускать цикл, который вы написали в функции, еще раз и возвращать следующее значение, пока значение не будет возвращено.
Генератор считается пустым после запуска функции, но больше не влияет на yield. Это может быть из-за того, что цикл закончился, или из-за того, что вы больше не удовлетворяете "если/еще".
Ваш код объяснил генератор:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
Caller:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidates list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
Этот код содержит несколько умных частей:
Цикл повторяется в списке, но список расширяется во время итерации цикла :-) Это краткий способ пройти через все эти вложенные данные, даже если это немного опасно, так как вы можете получить бесконечный цикл. В этом случае candid.extend(node._get_child_candidates (distance, min_dist, max_dist)) исчерпывает все значения генератора, но при этом продолжает создавать новые объекты генератора, которые будут генерировать значения, отличные от предыдущих, поскольку он не применяется к одному и тому же узел.
Метод extend() - это метод объекта списка, который ожидает итерацию и добавляет ее значения в список.
Обычно мы передаем ему список:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
Но в вашем коде он получает генератор, что хорошо, потому что:
Вам не нужно читать значения дважды. У вас может быть много детей, и вы не хотите, чтобы они все хранились в памяти. И это работает, потому что Python не заботится, является ли аргумент метода списком или нет. Python ожидает итерации, поэтому он будет работать со строками, списками, кортежами и генераторами! Это называется утка и является одной из причин, почему Python такой крутой. Но это другая история, для другого вопроса...
Вы можете остановиться здесь или прочитать немного, чтобы увидеть расширенное использование генератора:
Контроль истощения генератора
>>> class Bank(): # Let create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
Примечание: Для Python 3, useprint (. Corner_street_atm следующий()) или печать (следующая (corner_street_atm))
Это может быть полезно для различных вещей, таких как управление доступом к ресурсу.
Itertools, ваш лучший друг Модуль itertools содержит специальные функции для управления итерациями. Вы когда-нибудь хотели дублировать генератор? Цепочка двух генераторов? Группировать значения во вложенном списке с одной линией? Карта /Zip без создания другого списка?
Тогда просто импортируйте itertools.
Пример? Давайте рассмотрим возможные порядки заезда на скачки:
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
Понимание внутренних механизмов итерации Итерация - это процесс, подразумевающий итерации (реализующие метод iter()) и итераторы (реализующие метод next()). Итерации - это любые объекты, от которых вы можете получить итератор. Итераторы - это объекты, которые позволяют повторять итерации.
Ответ 18
Одним из основных моментов, связанных с ключевым словом Yield, является Lazy Execution. Теперь, что я подразумеваю под Lazy Execution, это выполнять при необходимости. Лучший способ выразить это - привести пример
Пример: не используется выход, т.е. нет отложенного выполнения.
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 и т.д.
Ответ 19
Он пытается привнести в Ruby Goodness:)
Концепция: Это пример кода Ruby, который выводит каждый элемент массива
rubyArray = [1,2,3,4,5,6,7,8,9,10]
rubyArray.each{|x|
puts x # do whatever with x
}
Массив каждого метода реализации дает контроль над вызывающим ( "puts x" ) с каждым элементом массива, который аккуратно представляется как x. Затем вызывающий может выполнять все, что ему нужно, с помощью x.
Однако .Net не идет полным ходом здесь. С#, кажется, связал выход с 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];
}