Некоторые помогают понять "доходность",
В моих вечных поисках, чтобы сосать меньше, я пытаюсь понять инструкцию yield, но я все время сталкиваюсь с той же ошибкой.
Тело [someMethod] не может быть блоком итератора, потому что "System.Collections.Generic.List <AClass> 'не является типом интерфейса итератора.
Это код, где я застрял:
foreach (XElement header in headersXml.Root.Elements()){
yield return (ParseHeader(header));
}
Что я делаю неправильно? Могу ли я использовать доход в итераторе? Тогда какой смысл?
В этом примере сказано, что List<ProductMixHeader>
не является типом интерфейса итератора.
ProductMixHeader
- это настраиваемый класс, но я полагаю, что List
- это тип интерфейса итератора, no?
- Edit -
Спасибо за все быстрые ответы.
Я знаю, что этот вопрос не все, что новые и те же ресурсы продолжают появляться.
Оказалось, я думал, что могу вернуть List<AClass>
как возвращаемый тип, но поскольку List<T>
не ленив, он не может. Изменение моего типа возврата на IEnumerable<T>
решило проблему: D
Несколько связанный вопрос (не стоит открывать новый поток): стоит ли IEnumerable<T>
использовать тип возвращаемого типа, если я уверен, что в 99% случаев я собираюсь идти. ToList() в любом случае? Каковы будут последствия для производительности?
Ответы
Ответ 1
Метод, использующий return return, должен быть объявлен как возвращающий один из следующих двух интерфейсов:
IEnumerable<SomethingAppropriate>
IEnumerator<SomethingApropriate>
(спасибо Jon и Marc для указания IEnumerator)
Пример:
public IEnumerable<AClass> YourMethod()
{
foreach (XElement header in headersXml.Root.Elements())
{
yield return (ParseHeader(header));
}
}
yield - ленивый производитель данных, только создающий другой элемент после первого поиска, тогда как возврат списка возвращает все за один раз.
Итак, есть разница, и вам нужно правильно объявить метод.
Для получения дополнительной информации прочитайте Jon answer here, который содержит очень полезные ссылки.
Ответ 2
Это сложная тема. Вкратце, это простой способ внедрения IEnumerable и его друзей. Компилятор создает вам машину состояний, преобразуя параметры и локальные переменные в переменные экземпляра в новом классе. Сложные вещи.
У меня есть несколько ресурсов по этому поводу:
Ответ 3
"yield" создает блок итератора - класс, сгенерированный компилятором, который может реализовать либо IEnumerable[<T>]
, либо IEnumerator[<T>]
. У Jon Skeet есть очень хорошее (и бесплатное) обсуждение этого в главе 6 С# в глубине.
Но в принципе - для использования "yield" ваш метод должен возвращать IEnumerable[<T>]
или IEnumerator[<T>]
. В этом случае:
public IEnumerable<AClass> SomeMethod() {
// ...
foreach (XElement header in headersXml.Root.Elements()){
yield return (ParseHeader(header));
}
}
Ответ 4
Список реализует 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<int> 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();
}
}
}
Ответ 5
Я настоятельно рекомендую использовать Reflector, чтобы посмотреть, что yield
действительно для вас. Вы сможете увидеть полный код класса, который генерирует компилятор для вас при использовании урожая, и я обнаружил, что люди понимают эту концепцию гораздо быстрее, когда видят результат низкого уровня (ну, уровень, я думаю).
Ответ 6
Чтобы понять yield
, вам нужно понять, когда использовать IEnumerator
и IEnumerable
(потому что вы должны использовать любой из них). Следующие примеры помогут вам понять разницу.
Сначала рассмотрим следующий класс, он реализует два метода: один возвращает IEnumerator<int>
, один возвращает IEnumerable<int>
. Я покажу вам, что есть большая разница в использовании, хотя код из двух методов выглядит аналогичным:
// 2 iterators, one as IEnumerator, one as IEnumerable
public class Iterator
{
public static IEnumerator<int> IterateOne(Func<int, bool> condition)
{
for(var i=1; condition(i); i++) { yield return i; }
}
public static IEnumerable<int> IterateAll(Func<int, bool> 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<T>
и IEnumerable<T>
являются Generics, они могут использоваться с любым типом. Однако для простоты я использовал int
в моих примерах для типа T
.
Это означает, что вы можете использовать один из типов возврата IEnumerator<ProductMixHeader>
или IEnumerable<ProductMixHeader>
(пользовательский класс, который вы упомянули в своем вопросе).
Тип List<ProductMixHeader>
не реализует ни один из этих интерфейсов, поэтому вы не можете использовать его таким образом. Но Пример 2.b) показывает, как вы можете создать список из него.
Ответ 7
Как выглядит метод, которым вы пользуетесь? Я не думаю, что это можно использовать только в одном цикле.
Например...
public IEnumerable<string> GetValues() {
foreach(string value in someArray) {
if (value.StartsWith("A")) { yield return value; }
}
}
Ответ 8
@Ответ от PI помог мне понять, почему и почему он используется. Один (основной) вариант использования для доходности находится в циклах "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<int> FindPrimesWithYield()
{
int i;
for (i = 1; i < 2147483647; i++)
{
if (isPrime(i))
{
yield return i;
}
}
}
public IEnumerable<int> FindPrimesWithoutYield()
{
var primes = new List<int>();
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();
}
}
}