Некоторая помощь в понимании "доходности" - PullRequest
36 голосов
/ 25 ноября 2008

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

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

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

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

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

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

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

Ответы [ 8 ]

32 голосов
/ 25 ноября 2008

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

IEnumerable<SomethingAppropriate>
IEnumerator<SomethingApropriate>

(спасибо Джону и Марку за указание на IEnumerator)

Пример:

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

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

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

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

15 голосов
/ 25 ноября 2008

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

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

8 голосов
/ 25 ноября 2008

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

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

public IEnumerable<AClass> SomeMethod() {
    // ...
    foreach (XElement header in headersXml.Root.Elements()){
        yield return (ParseHeader(header));                
    }
}
3 голосов
/ 25 ноября 2008

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

3 голосов
/ 25 ноября 2008

Список реализует 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();
        }
    }
}
2 голосов
/ 17 июня 2015

Чтобы понять 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> являются Общими, они могут использоваться с любым типом. Однако для простоты я использовал int в моих примерах для типа T.

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

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

Если вы создаете список, добавляя .ToList(), то подразумевается, что он создаст список всех элементов в памяти, тогда как IEnumerable позволяет лениво создавать свои элементы - с точки зрения производительности, он означает, что элементы перечисляются как раз вовремя - как можно позже, но как только вы используете .ToList(), все элементы создаются в памяти. LINQ пытается оптимизировать производительность таким образом за кулисами.

DotNetFiddle для всех примеров

1 голос
/ 11 июля 2014

@ Ответ Яна П очень помог мне понять доходность и почему она используется. Один (основной) вариант использования yield - в циклах «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();
        }
    }
}
0 голосов
/ 25 ноября 2008

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

Например ...

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