Поделиться через


Оператор yield — укажите следующий элемент

yield Оператор используется в итераторе для предоставления следующего значения или сигнала о завершении итерации. Инструкция yield имеет две следующие формы:

  • yield return: чтобы указать следующее значение в итерации, как показано в следующем примере:

    foreach (int i in ProduceEvenNumbers(9))
    {
        Console.Write(i);
        Console.Write(" ");
    }
    // Output: 0 2 4 6 8
    
    IEnumerable<int> ProduceEvenNumbers(int upto)
    {
        for (int i = 0; i <= upto; i += 2)
        {
            yield return i;
        }
    }
    
  • yield break: для явного сигнала о завершении итерации, как показано в следующем примере:

    Console.WriteLine(string.Join(" ", TakeWhilePositive(new int[] {2, 3, 4, 5, -1, 3, 4})));
    // Output: 2 3 4 5
    
    Console.WriteLine(string.Join(" ", TakeWhilePositive(new int[] {9, 8, 7})));
    // Output: 9 8 7
    
    IEnumerable<int> TakeWhilePositive(IEnumerable<int> numbers)
    {
        foreach (int n in numbers)
        {
            if (n > 0)
            {
                yield return n;
            }
            else
            {
                yield break;
            }
        }
    }
    

    Итерация также завершается, когда элемент управления достигает конца итератора.

В предыдущих примерах возвращаемый тип итераторов (в негенерических случаях используется IEnumerable<T>IEnumerable в качестве возвращаемого типа итератора). Можно также использовать IAsyncEnumerable<T> в качестве возвращаемого типа итератора. Это делает асинхронный итератор. Используйте инструкцию await foreach для итерации результатов итератора, как показано в следующем примере:

await foreach (int n in GenerateNumbersAsync(5))
{
    Console.Write(n);
    Console.Write(" ");
}
// Output: 0 2 4 6 8

async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
    for (int i = 0; i < count; i++)
    {
        yield return await ProduceNumberAsync(i);
    }
}

async Task<int> ProduceNumberAsync(int seed)
{
    await Task.Delay(1000);
    return 2 * seed;
}

IEnumerator<T> или IEnumerator также может быть типом возвращаемого итератора. Используйте эти типы возвращаемых данных при реализации GetEnumerator метода в следующих сценариях:

  • Вы разрабатываете тип, реализующий IEnumerable<T> или IEnumerable интерфейс.

  • Вы добавляете метод экземпляра или , чтобы включить итерацию экземпляра типа с GetEnumerator, как показано в следующем примере:

    public static void Example()
    {
        var point = new Point(1, 2, 3);
        foreach (int coordinate in point)
        {
            Console.Write(coordinate);
            Console.Write(" ");
        }
        // Output: 1 2 3
    }
    
    public readonly record struct Point(int X, int Y, int Z)
    {
        public IEnumerator<int> GetEnumerator()
        {
            yield return X;
            yield return Y;
            yield return Z;
        }
    }
    

Инструкции yield нельзя использовать в следующих инструкциях:

  • методы с параметрами в, refили out.
  • лямбда-выражения и анонимные методы.
  • небезопасные блоки. До C# 13 yield недопустимо в любом методе с блоком unsafe . Начиная с C# 13, можно использовать yield в методах с unsafe блоками, но не в блоке unsafe .
  • yield return и yield break не может использоваться в блоках catch и , наконец , или в блоках try с соответствующим catch блоком. Операторы yield return и инструкции можно использовать в блоке yield break без try блоков, а только catch блок.finally

using операторы в итераторах

Инструкции можно использовать using в методах итератора. Так как using операторы компилируются в try блоки с finally предложениями (и без catch блоков), они работают правильно с итераторами. Удаленные ресурсы правильно управляются во время выполнения итератора:

Console.WriteLine("=== Using in Iterator Example ===");

// Demonstrate that using statements work correctly in iterators
foreach (string line in ReadLinesFromResource())
{
    Console.WriteLine($"Read: {line}");
    // Simulate processing only first two items
    if (line == "Line 2") break;
}

Console.WriteLine("Iteration stopped early - resource should still be disposed.");

static IEnumerable<string> ReadLinesFromResource()
{
    Console.WriteLine("Opening resource...");
    using var resource = new StringWriter(); // Use StringWriter as a simple IDisposable
    resource.WriteLine("Resource initialized");
    
    // These lines would typically come from the resource (e.g., file, database)
    string[] lines = { "Line 1", "Line 2", "Line 3", "Line 4" };
    
    foreach (string line in lines)
    {
        Console.WriteLine($"About to yield: {line}");
        yield return line;
        Console.WriteLine($"Resumed after yielding: {line}");
    }
    
    Console.WriteLine("Iterator completed - using block will dispose resource.");
}

Как показано в предыдущем примере, ресурс, полученный в using инструкции, остается доступным во время выполнения итератора, даже если итератор приостанавливает и возобновляет выполнение операторов yield return . Ресурс удаляется при завершении итератора (путем достижения конца или через yield break) или при удалении самого итератора (например, когда вызывающий объект прерывается из перечисления рано).

Выполнение итератора

Вызов итератора не выполняется немедленно, как показано в следующем примере:

var numbers = ProduceEvenNumbers(5);
Console.WriteLine("Caller: about to iterate.");
foreach (int i in numbers)
{
    Console.WriteLine($"Caller: {i}");
}

IEnumerable<int> ProduceEvenNumbers(int upto)
{
    Console.WriteLine("Iterator: start.");
    for (int i = 0; i <= upto; i += 2)
    {
        Console.WriteLine($"Iterator: about to yield {i}");
        yield return i;
        Console.WriteLine($"Iterator: yielded {i}");
    }
    Console.WriteLine("Iterator: end.");
}
// Output:
// Caller: about to iterate.
// Iterator: start.
// Iterator: about to yield 0
// Caller: 0
// Iterator: yielded 0
// Iterator: about to yield 2
// Caller: 2
// Iterator: yielded 2
// Iterator: about to yield 4
// Caller: 4
// Iterator: yielded 4
// Iterator: end.

Как показано в предыдущем примере, при запуске итерации по результату итератора выполняется итератор до достижения первой yield return инструкции. Затем выполнение итератора приостановлено, и вызывающий объект получает первое значение итерации и обрабатывает его. При каждой последующей итерации выполнение итератора возобновляется после yield return инструкции, вызвавшей предыдущую приостановку и продолжающуюся до достижения следующей yield return инструкции. Итерация завершается, когда элемент управления достигает конца итератора или yield break оператора.

Спецификация языка C#

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

См. также