Trong C#, từ khóa yield return cho phép tạo ra một chuỗi giá trị được cung cấp theo yêu cầu, thay vì tải toàn bộ dữ liệu vào bộ nhớ ngay lập tức. Điều này đặc biệt hữu ích khi làm việc với các tập hợp lớn hoặc nguồn dữ liệu vô hạn.
Cách sử dụng cơ bản
Dưới đây là ví dụ minh họa cách dùng yield return để sinh ra một dãy số ngẫu nhiên:
using System;
using System.Collections.Generic;
class Program
{
private static Random generator = new Random();
static IEnumerable<int> GenerateNumbers(int count)
{
for (int index = 0; index < count; index++)
{
yield return generator.Next(1, 11);
}
}
static void Main()
{
foreach (int value in GenerateNumbers(6))
{
Console.WriteLine(value);
}
Console.ReadKey();
}
}
Khi chạy đoạn mã này, mỗi lần vòng lặp foreach yêu cầu phần tử tiếp theo, phương thức GenerateNumbers sẽ tạm dừng tại vị trí yield return, lưu trạng thái hiện tại, rồi tiếp tục từ điểm đó khi được gọi lại — điều này tạo nên hiệu ứng "cung cấp từng phần tử theo nhu cầu".
Nội tại đằng sau yield: một lớp lồng ghép
Thực tế, yield return là một cú pháp rút gọn (syntactic sugar) do compiler xử lý. Khi biên dịch, nó tạo ra một lớp nội bộ triển khai cả IEnumerable<T> và IEnumerator<T>. Lớp này duy trì trạng thái thông qua một trường kiểu int gọi là _state, giúp xác định bước hiện tại trong quá trình duyệt.
Ví dụ, ta có thể tự xây dựng một lớp tương đương:
class NumberGenerator : IEnumerable<int>, IEnumerator<int>
{
private int totalItems;
private int currentIndex;
private int currentValue;
private int state;
public NumberGenerator(int size)
{
totalItems = size;
state = 0;
currentIndex = 0;
}
public int Current => currentValue;
object IEnumerator.Current => Current;
public bool MoveNext()
{
switch (state)
{
case 0:
currentIndex = 0;
goto case 1;
case 1:
state = 1;
if (currentIndex >= totalItems) return false;
currentValue = generator.Next(1, 11);
state = 2;
return true;
case 2:
currentIndex++;
goto case 1;
default:
return false;
}
}
public void Reset() => throw new NotImplementedException();
public void Dispose() { }
public IEnumerator<int> GetEnumerator() => this;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Lớp này mô phỏng hành vi của yield return bằng cách sử dụng chuyển đổi trạng thái thông qua goto và kiểm soát luồng thực thi dựa trên giá trị của state.
Sử dụng yield break để kết thúc sớm
Đôi khi cần dừng việc sản xuất phần tử trước khi hoàn tất vòng lặp. Trong trường hợp này, yield break là công cụ phù hợp.
static IEnumerable<int> GenerateUntilZero()
{
while (true)
{
int temp = generator.Next(100);
if (temp % 10 == 0)
{
yield break; // Dừng ngay lập tức nếu gặp số chia hết cho 10
}
yield return temp;
}
}
Việc sử dụng yield break không chỉ làm giảm tài nguyên mà còn cho phép kiểm soát logic ở mức độ sinh dữ liệu, thay vì phải xử lý điều kiện bên ngoài vòng lặp.