Некоторые помогают понять «доходность»,

В моем вечном стремлении меньше сосать, я пытаюсь понять утверждение «yield», но я все время сталкиваюсь с той же ошибкой.

Тело [someMethod] не может быть блоком iteratorа, потому что «System.Collections.Generic.List » не является типом интерфейса iteratorа.

Это код, в котором я застрял:

foreach (XElement header in headersXml.Root.Elements()){ yield return (ParseHeader(header)); } 

Что я делаю не так? Могу ли я использовать доход в iteratorе? Тогда в чем смысл? В этом примере сказано, что List не является типом интерфейса iteratorа. ProductMixHeader – это настраиваемый class, но я думаю, что List – это тип интерфейса iteratorа, нет?

–Редактировать–
Спасибо за все быстрые ответы.
Я знаю, что этот вопрос не все, что новые и те же ресурсы продолжают появляться.
Оказалось, я думал, что могу вернуть List как возвращаемый тип, но поскольку List не ленив, он не может. Изменение моего возвращаемого типа на IEnumerable разрешило проблему: D

Несколько связанный вопрос (не стоит открывать новый stream): стоит ли IEnumerable как возвращаемый тип, если я уверен, что в 99% случаев я собираюсь идти. ToList () в любом случае? Каковы будут последствия для производительности?

Метод, возвращающий доход, должен быть объявлен как возвращающий один из следующих двух интерфейсов:

 IEnumerable IEnumerator 

(спасибо Jon и Marc за указание IEnumerator)

Пример:

 public IEnumerable YourMethod() { foreach (XElement header in headersXml.Root.Elements()) { yield return (ParseHeader(header)); } } 

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

Таким образом, есть разница, и вам нужно объявить метод правильно.

Для получения дополнительной информации прочитайте здесь ответ Джона , в котором содержатся очень полезные ссылки.

Это сложная тема. В двух словах, это простой способ внедрения IEnumerable и его друзей. Компилятор создает вам машину состояний, преобразуя параметры и локальные переменные в переменные экземпляра в новом classе. Сложный материал.

У меня есть несколько ресурсов по этому поводу:

  • Глава 6 C # в глубине (бесплатная загрузка с этой страницы)
  • Итераторы, блоки iteratorов и трубопроводы данных (статья)
  • Детали реализации блока iteratorа (статья)

«yield» создает блок iteratorа – сгенерированный компилятором class, который может реализовать либо IEnumerable[] либо IEnumerator[] . У Jon Skeet есть очень хорошее (и бесплатное) обсуждение этого в главе 6 C # в Depth .

Но в принципе – для использования «yield» ваш метод должен возвращать IEnumerable[] или IEnumerator[] . В этом случае:

 public IEnumerable SomeMethod() { // ... foreach (XElement header in headersXml.Root.Elements()){ yield return (ParseHeader(header)); } } 

Список реализует Ienumerable.

Вот пример, который может пролить свет на то, что вы пытаетесь узнать. Я написал это около 6 месяцев

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YieldReturnTest { public class PrimeFinder { private Boolean isPrime(int integer) { if (0 == integer) return false; if (3 > integer) return true; for (int i = 2; i < integer; i++) { if (0 == integer % i) return false; } return true; } public IEnumerable FindPrimes() { int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { yield return i; } } } } class Program { static void Main(string[] args) { PrimeFinder primes = new PrimeFinder(); foreach (int i in primes.FindPrimes()) { Console.WriteLine(i); Console.ReadLine(); } Console.ReadLine(); Console.ReadLine(); } } } 

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

Чтобы понять yield , вам нужно понять, когда использовать IEnumerator и IEnumerable (потому что вы должны использовать любой из них). Следующие примеры помогут вам понять разницу.

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

 // 2 iterators, one as IEnumerator, one as IEnumerable public class Iterator { public static IEnumerator IterateOne(Func condition) { for(var i=1; condition(i); i++) { yield return i; } } public static IEnumerable IterateAll(Func condition) { for(var i=1; condition(i); i++) { yield return i; } } } 

Теперь, если вы используете IterateOne вы можете сделать следующее:

  // 1. Using IEnumerator allows to get item by item var i=Iterator.IterateOne(x => true); // iterate endless // 1.a) get item by item i.MoveNext(); Console.WriteLine(i.Current); i.MoveNext(); Console.WriteLine(i.Current); // 1.b) loop until 100 int j; while (i.MoveNext() && (j=i.Current)<=100) { Console.WriteLine(j); } 

1.a) печатает:

1
2

1.b) печатает:

3
4
...
100

потому что он продолжает считать сразу после выполнения инструкций 1.a).

Вы можете видеть, что вы можете продвигать элемент по элементу с помощью MoveNext() .


В отличие от этого, IterateAll позволяет использовать foreach а также инструкции LINQ для большего комфорта:

  // 2. Using IEnumerable makes looping and LINQ easier var k=Iterator.IterateAll(x => x<100); // limit iterator to 100 // 2.a) Use a foreach loop foreach(var x in k){ Console.WriteLine(x); } // loop // 2.b) LINQ: take 101..200 of endless iteration var lst=Iterator.IterateAll(x=>true).Skip(100).Take(100).ToList(); // LINQ: take items foreach(var x in lst){ Console.WriteLine(x); } // output list 

2.a) печатает:

1
2
...
99

2.b) печатает:

101
102
...
200


Примечание. Поскольку IEnumerator и IEnumerable являются Generics, они могут использоваться с любым типом. Однако для простоты я использовал int в своих примерах для типа T

Это означает, что вы можете использовать один из типов возвращаемых данных IEnumerator или IEnumerable (пользовательский class, который вы упомянули в своем вопросе).

Тип List не реализует ни один из этих интерфейсов, поэтому вы не можете использовать его таким образом. Но пример 2.b) показывает, как вы можете создать список из него.

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

Например…

 public IEnumerable GetValues() { foreach(string value in someArray) { if (value.StartsWith("A")) { yield return value; } } } 

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

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YieldReturnTest { public class PrimeFinder { private Boolean isPrime(int integer) { if (0 == integer) return false; if (3 > integer) return true; for (int i = 2; i < integer; i++) { if (0 == integer % i) return false; } return true; } public IEnumerable FindPrimesWithYield() { int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { yield return i; } } } public IEnumerable FindPrimesWithoutYield() { var primes = new List(); int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { primes.Add(i); } } return primes; } } class Program { static void Main(string[] args) { PrimeFinder primes = new PrimeFinder(); Console.WriteLine("Finding primes until 7 with yield...very fast..."); foreach (int i in primes.FindPrimesWithYield()) // FindPrimesWithYield DOES NOT iterate over all integers at once, it returns item by item { if (i > 7) { break; } Console.WriteLine(i); //Console.ReadLine(); } Console.WriteLine("Finding primes until 7 without yield...be patient it will take lonkg time..."); foreach (int i in primes.FindPrimesWithoutYield()) // FindPrimesWithoutYield DOES iterate over all integers at once, it returns the complete list of primes at once { if (i > 7) { break; } Console.WriteLine(i); //Console.ReadLine(); } Console.ReadLine(); Console.ReadLine(); } } } 
Давайте будем гением компьютера.