2008-11-25 5 views
37

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

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

Это код, где я застрял:

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

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

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

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

+6

Мне нравится способ, с помощью которого можно найти «стремление к сосать меньше» ;-). – 2008-11-25 14:21:04

+0

Этот почти идентичный вопрос имеет ссылку на некоторые хорошие вещи Рэймонда Чена: http://stackoverflow.com/questions/39476/what-is-the-yield-keyword-used-for-in-c – 2008-11-25 14:24:32

ответ

32

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

IEnumerable<SomethingAppropriate> 
IEnumerator<SomethingApropriate> 

(благодаря Jon и Marc за указание IEnumerator)

Пример:

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

выход - ленивый производитель данных, производящий еще один товар af ter был извлечен первым, тогда как возврат списка вернет все за один раз.

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

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

+2

Для записи: или IEnumerator [] – 2008-11-25 14:23:18

+2

Он также может быть объявлен для возврата IEnumerator или IEnumerator . – 2008-11-25 14:23:25

0

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

Например ...

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

Список реализует 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(); 
     } 
    } 
} 
8

«выход» создает итератора блок - компилятор сгенерированный класс, который может реализовать либо IEnumerable[<T>] или IEnumerator[<T>]. У Джона Скита очень хорошее (и бесплатное) обсуждение этого в главе 6 из C# in Depth.

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

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

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

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

3

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

0

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

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(); 
     } 
    } 
} 
1

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

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

// 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.б) печатает:

3
4
...
100

, потому что он по-прежнему рассчитывает сразу после 1.а) заявления были выполнены.

Вы можете видеть, что вы можете продвигать товар по пунктам, используя 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.а) печатает:

1
2
. ..
99

2б) печатает:

101
102
...
200


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

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

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