C# - C Sharp: yield
Bạn sử dụng câu lệnh yield
trong một trình vòng lặp để cung cấp giá trị tiếp theo hoặc báo hiệu sự kết thúc của một vòng lặp. Câu lệnh yield
có hai hình thức sau:
yield return
yield return để cung cấp giá trị tiếp theo trong lần lặp, như ví dụ sau:
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:
yield break để báo hiệu một cách tường minh sự kết thúc của vòng lặp, như ví dụ sau:
Console.WriteLine(string.Join(" ", TakeWhilePositive([2, 3, 4, 5, -1, 3, 4]))); // Output: 2 3 4 5 Console.WriteLine(string.Join(" ", TakeWhilePositive([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; } } }
Việc lặp lại cũng kết thúc khi điều khiển đạt đến cuối vòng lặp.
Trong các ví dụ trên, kiểu trả về của trình vòng lặp là IEnumerable<T> (trong các trường hợp non-generic, hãy sử dụng IEnumerable làm kiểu trả về của trình vòng lặp). Bạn cũng có thể sử dụng IAsyncEnumerable<T> làm kiểu trả về của trình vòng lặp. Điều đó làm cho một trình vòng lặp không đồng bộ. Sử dụng câu lệnh await foreach
để lặp lại kết quả của iterator, như ví dụ sau cho thấy:
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> hoặc IEnumerator cũng có thể là kiểu trả về của trình vòng lặp. Điều đó rất hữu ích khi bạn triển khai phương thức GetEnumerator
trong các trường hợp sau:
-
Bạn thiết kế kiểu triển khai giao diện IEnumerable<T> hoặc IEnumerable.
-
Bạn thêm một phiên bản hoặc phương thức mở rộng
GetEnumerator
để cho phép lặp qua phiên bản của kiểu đó bằng câu lệnhforeach
, như ví dụ sau đây cho thấy:
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; } }
Bạn không thể sử dụng các câu lệnh yield
trong:
- các phương thức có tham số in, ref hoặc out
- biểu thức lambda và phương thức ẩn danh
- phương thức chứa các khối không an toàn
Thực thi một iterator
Lệnh gọi của một trình vòng lặp không thực hiện nó ngay lập tức, như ví dụ sau đây cho thấy:
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.
Như ví dụ trên cho thấy, khi bạn bắt đầu lặp lại kết quả của một trình vòng lặp, một trình vòng lặp sẽ được thực thi cho đến khi yield return
đạt đến câu lệnh đầu tiên. Sau đó, việc thực thi một trình lặp bị tạm dừng và người gọi sẽ nhận giá trị lặp đầu tiên và xử lý nó. Ở mỗi lần lặp tiếp theo, việc thực thi của một trình vòng lặp sẽ tiếp tục sau câu lệnh yield return
gây ra sự tạm dừng trước đó và tiếp tục cho đến khi yield return
đạt được câu lệnh tiếp theo. Việc lặp lại hoàn thành khi điều khiển đạt đến cuối một trình vòng lặp hoặc một câu lệnh yield break
.
Đặc tả ngôn ngữ C#
Để biết thêm thông tin, hãy xem phần câu lệnh yield của đặc tả ngôn ngữ C#.