C# – Tìm hiểu về từ khóa ‘yield’ & cơ chế yielding (sưu tầm)

yield” – Một từ khóa có vẻ lạ lẫm với bạn nhưng đã được ứng dụng từ lâu trong lĩnh vực lập trình và trong khá nhiều ngôn ngữ. Từ khóa này gắn liền với các kĩ thuật như Generator, Coroutine và các đối tượng iterator. Ta sẽ khám phá cơ chế và cách sử dụng của từ khóa này như thế nào.

Có thể hiểu đơn giản là “yield” sẽ kết hợp với từ khóa “return” để trả về các giá trị từ trong vòng lặp. Sau đó, nó có thể trở lại vòng lặp và tiếp tục cho phần tử tiếp theo.

Ở đây là “yield return” sẽ không làm cho phương thức chứa nó kết thúc mà vẫn tiếp tục chạy cho đến khi kết thúc vòng lặp và thực thi xong câu lệnh cuối cùng. Muốn kết thúc phương thức bạn phải dùng một cặp từ khóa khác là “yield break”.

Quy định

  • Không đặt “yield” trong các khối unsafe.
  • Các tham số của phương thức, toán tử, accessor (getter/setter) không được dùng các từ khóa ref hoặc out.
  • yield return” chỉ có thể được đặt trong khối try nếu như nó được theo sau bởi khối finally.
  • yield break” có thể đặt trong các khối try và catch nhưng không được đặt trong khối finally.
  • Không dùng “yield” trong anonymous method.

Cách dùng

Để sử dụng “yield return”, bạn chỉ cần tạo một phương thức với kiểu trả về là một IEnumerable (mảng và collection trong .Net đều implement interface IEnumerable)  với vòng lặp và dùng “yield return” để trả về các giá trị cần thiết trong thân vòng lặp.
Ví dụ thay vì viết một phương thức Foo1() để lấy một mảng int từ 0 đến một số nào đó như dưới đây:
public static IEnumerable Foo1(int number)
{
    int[] numbers = new int[number];
    for (int i = 0; i < number; i++)
    {
        numbers[i] = i;
    }
    return numbers;
}
Bạn có thể thay thế phương thức trên bằng phương thức sau:
public static IEnumerable Foo2(int number)
{
    for (int i = 0; i < number; i++)
    {
        yield return i;
    }
}
Rõ ràng là thay vì trả về nguyên một đối tượng IEnumerable, ta sẽ trả về từng phần tử riêng lẻ và khi kết thúc phương thức, bạn không cần phải return thêm bất kì đối tượng nào.
Bạn có thể chạy thử đoạn mã sau:
Listing 1:
using System;
using System.Collections;
public class YieldTest
{
    public static IEnumerable Foo2(int number)
    {
        for (int i = 0; i < number; i++)
        {
            yield return i;
        }
    }
    static void Main()
    {
        foreach (var item in Foo2(10))
        {
            Console.Write(item);
        }
        Console.Read();
    }
}
Output: 0123456789
Bạn có thể đặt break point tại dòng 10 và dòng 18 (hai dòng được highlight) và chức năng debug continue (F5) thì sẽ thấy rằng hai break point này sẽ thay phiên nhau được kích hoạt. Điều này chứng tỏ rằng chương trình có thể nhảy qua lại giữa hai phương thức mà không làm mất trạng thái hiện tại của chúng.
Giả sử bạn muốn thoát ra khỏi phương thức khi i = 5, ta cần sửa lại phương thức Foo2() trên:
public static IEnumerable Foo2(int number)
{
    for (int i = 0; i < number; i++)
    {
        if (i == 5)
            yield break;
        yield return i;
    }
}

Cơ chế hoạt động

Trong phương thức Foo2() mới này, bạn sẽ không nhận thấy sự khác biệt nếu thay yield break bằng break bởi vì khi vòng lặp bị kết thúc bằng break thì phương thức cũng kết thúc theo. Muốn kiểm tra sự khác biệt, bạn hãy dùng Console.WriteLine() in gì ra màn hình ở cuối phương thức Foo2() trên.
Khi dùng ILDasm.exe phân tích Listing 1 ta nhận thấy compiler đã tạo ra một lớp con trong lớp YieldTest:
YieldTest ILDasm
Hoặc dùng một Reflector của hãng thứ ba để disassemble lớp này ra mã C# ta được code tổng quát sau:
private sealed class d__0 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable
{
    // Fields
    private int 1__state;
    private object 2__current;
    public int 3__number;
    private int l__initialThreadId;
    public int 5__1;
    public int number;
    // Methods
    [DebuggerHidden]
    public d__0(int 1__state);
    private bool MoveNext();
    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator();
    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator();
    [DebuggerHidden]
    void IEnumerator.Reset();
    void IDisposable.Dispose();
    // Properties
    object IEnumerator.Current { [DebuggerHidden] get; }
    object IEnumerator.Current { [DebuggerHidden] get; }
}
Như vậy bạn có thể rằng đây thực chất là một lớp làm việc dựa trên IEnumertor với các phương thức chính là MoveNext(), Reset() và property Current.
Dựa theo phương pháp này, bạn có thể dễ dàng cài đặt và tự tạo ra kĩ thuật tương tự “yield” để dùng trong các ngôn ngữ không hỗ trợ từ khóa này chẳng hạn như VB.Net.
Sưu tầm từ blog : http://yinyangit.wordpress.com

Leave a Reply

Related Post