Может кто-нибудь демистифицировать ключевое слово yield? - PullRequest
28 голосов
/ 25 августа 2009

Я видел, что ключевое слово yield довольно часто используется в переполнении стека и блогах. Я не использую LINQ . Может кто-нибудь объяснить ключевое слово yield?

Я знаю, что подобные вопросы существуют. Но никто на самом деле не объясняет, что его используют на простом языке.

Ответы [ 9 ]

32 голосов
/ 25 августа 2009

Безусловно, лучшим объяснением этого (которое я видел) является книга Джона Скита - и эта глава бесплатна! Глава 6, C # в глубину . Я не могу добавить здесь ничего, что не охвачено.

Тогда купи книгу; Вы будете лучшим программистом C # для этого.


В: Почему я не написал здесь более длинный ответ (перефразировано из комментариев); просто. Как заметил Эрик Липперт ( здесь ), конструкция yield (и магия, которая за ней стоит) - это единственный самый сложный бит кода в компиляторе C # , и попробовать и описать это в кратком ответе здесь наивно в лучшем случае. У yield столько нюансов, что IMO лучше обратиться к уже существующему (и полностью квалифицированному) ресурсу.

В блоге Эрика теперь есть 7 записей (и это только последние), в которых обсуждается yield. У меня есть огромное уважение к Эрику, но его блог, вероятно, более уместен как "дополнительная информация" для людей, которые знакомы с предметом (yield в данном случае ), так как в нем обычно описывается множество вопросов design . Лучше всего сделать в контексте разумного основания.

(и да, глава 6 загружает ; я подтвердил ...)

30 голосов
/ 25 августа 2009

Ключевое слово yield используется с методами, которые возвращают IEnumerable<T> или IEnumerator<T>, и заставляет компилятор генерировать класс, который реализует необходимые условия для использования итератора. Например.

public IEnumerator<int> SequenceOfOneToThree() {
    yield return 1;
    yield return 2;
    yield return 3;
}

Учитывая вышеизложенное, компилятор сгенерирует класс, который реализует IEnumerator<int>, IEnumerable<int> и IDisposable (фактически он также реализует неуниверсальные версии IEnumerable и IEnumerator).

Это позволяет вам вызывать метод SequenceOfOneToThree в цикле foreach следующим образом

foreach(var number in SequenceOfOneToThree) {
    Console.WriteLine(number);
}

Итератор - это конечный автомат, поэтому каждый раз, когда вызывается yield, записывается позиция в методе. Если итератор перемещается к следующему элементу, метод возобновляется сразу после этой позиции. Таким образом, первая итерация возвращает 1 и отмечает эту позицию. Следующий итератор возобновляет сразу после одного и, следовательно, возвращает 2 и так далее.

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

18 голосов
/ 26 августа 2009

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

операторы доходности и разрыва доходности чаще всего используются для обеспечения "отложенной оценки" коллекции.

Это означает, что когда вы получаете значение метода, который использует возвращаемую доходность, коллекция вещей, которые вы пытаетесь получить, еще не существует вместе (по сути, она пуста). По мере их обхода (используя foreach) он будет выполнять метод в это время и получит следующий элемент в перечислении.

Некоторые свойства и методы вызовут одновременную оценку всего перечисления (например, «Подсчет»).

Вот краткий пример разницы между возвратом коллекции и доходностью:

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };

public IEnumerable<string> GetYieldEnumerable()
{
    foreach (var name in names)
        yield return name;
}

public IEnumerable<string> GetList()
{
    var list = new List<string>();
    foreach (var name in names)
        list.Add(name);

    return list;
}

// we're going to execute the GetYieldEnumerable() method
// but the foreach statement inside it isn't going to execute
var yieldNames = GetNamesEnumerable();

// now we're going to execute the GetList() method and
// the foreach method will execute
var listNames = GetList();

// now we want to look for a specific name in yieldNames.
// only the first two iterations of the foreach loop in the 
// GetYieldEnumeration() method will need to be called to find it.
if (yieldNames.Contains("Jim")
    Console.WriteLine("Found Jim and only had to loop twice!");

// now we'll look for a specific name in listNames.
// the entire names collection was already iterated over
// so we've already paid the initial cost of looping through that collection.
// now we're going to have to add two more loops to find it in the listNames
// collection.
if (listNames.Contains("Jim"))
    Console.WriteLine("Found Jim and had to loop 7 times! (5 for names and 2 for listNames)");

Это также можно использовать, если вам нужно получить ссылку на перечисление до того, как исходные данные будут иметь значения. Например, если коллекция имен не была завершена, чтобы начать с:

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };

public IEnumerable<string> GetYieldEnumerable()
{
    foreach (var name in names)
        yield return name;
}

public IEnumerable<string> GetList()
{
    var list = new List<string>();
    foreach (var name in names)
        list.Add(name);

    return list;
}

var yieldNames = GetNamesEnumerable();

var listNames = GetList();

// now we'll change the source data by renaming "Jim" to "Jimbo"
names[1] = "Jimbo";

if (yieldNames.Contains("Jimbo")
    Console.WriteLine("Found Jimbo!");

// Because this enumeration was evaluated completely before we changed "Jim"
// to "Jimbo" it isn't going to be found
if (listNames.Contains("Jimbo"))
    // this can't be true
else
   Console.WriteLine("Couldn't find Jimbo, because he wasn't there when I was evaluated.");
10 голосов
/ 25 августа 2009

Ключевое слово yield - это удобный способ написать IEnumerator. Например:

public static IEnumerator<int> Range(int from, int to)
{
    for (int i = from; i < to; i++)
    {
        yield return i;
    }
}

преобразуется компилятором C # во что-то похожее на:

public static IEnumerator<int> Range(int from, int to)
{
    return new RangeEnumerator(from, to);
}

class RangeEnumerator : IEnumerator<int>
{
    private int from, to, current;

    public RangeEnumerator(int from, int to)
    {
        this.from = from;
        this.to = to;
        this.current = from;
    }

    public bool MoveNext()
    {
        this.current++;
        return this.current < this.to;
    }

    public int Current
    {
        get
        {
            return this.current;
        }
    }
}
6 голосов
/ 25 августа 2009

Ознакомьтесь с документацией MSDN и примером. По сути, это простой способ создать итератор в C #.

public class List
{
    //using System.Collections;
    public static IEnumerable Power(int number, int exponent)
    {
        int counter = 0;
        int result = 1;
        while (counter++ < exponent)
        {
            result = result * number;
            yield return result;
        }
    }

    static void Main()
    {
        // Display powers of 2 up to the exponent 8:
        foreach (int i in Power(2, 8))
        {
            Console.Write("{0} ", i);
        }
    }
}
4 голосов
/ 25 августа 2009

Серия Эрика Уайта по функциональному программированию вполне стоит прочитать полностью, но запись по Выходу является таким же ясным объяснением, как я видел.

3 голосов
/ 25 августа 2009

yield не имеет прямого отношения к LINQ, а скорее к блокам итератора . В связанной статье MSDN подробно рассказывается об этой языковой функции. См. Особенно раздел Использование итераторов . Подробные сведения о блоках итераторов см. В последнем блоге Эрика Липперта posts об этой функции. Для общей концепции см. Статью Википедии об итераторах.

2 голосов
/ 30 сентября 2009

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

Я использую это:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

А в другом месте:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

Я пытался найти oneliner, который делает это, но это невозможно, так как yield не работает внутри блоков анонимных методов.

EDIT:

Еще лучше, используйте общий клонер List:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}
0 голосов
/ 25 августа 2009

Позвольте мне добавить ко всему этому. Доходность не является ключевым словом. Это будет работать, только если вы используете «yield return», кроме того, что оно будет работать как обычная переменная.

Используется для возврата итератора из функции. Вы можете искать дальше на этом. Я рекомендую поиск "Возвращение массива против итератора"

...