Может ли кто-то демистифицировать ключевое слово yield?

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

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

Ответ 1

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

Затем купите книгу; вы будете лучшим программистом на С#.


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

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

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

Ответ 2

Ключевое слово 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.

Ответ 3

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

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

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

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

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

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.");

Ответ 4

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

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

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

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;
        }
    }
}

Ответ 5

Взгляните на документацию MSDN и пример. По сути, это простой способ создать итератор в С#.

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

Ответ 7

yield не имеет прямого отношения к LINQ, а скорее к итератора. Связанная MSDN статья дает подробные сведения об этой языковой функции. См., В частности, раздел Using Iterators. Подробные сведения об итераторных блоках см. В недавнем блоге Eric Lippert сообщения об этой функции. Для общей концепции см. Статью статьи в итераторах.

Ответ 8

Я придумал это, чтобы преодолеть недостаток .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, который делает это, но это невозможно, из-за того, что выход не работает внутри блоков анонимного метода.

EDIT:

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

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}

Ответ 9

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

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