+1

IEnumerator và IEnumerable

Phuongne, Th11 04, 2019


Người xưa đã có câu: “class nào có implement từ interface IEnumerable đều có thể sử dụng foreach“. Vậy điều này có phải là sự thật? Và IEnumerator rốt cuộc là gì?

Đầu tiên chúng ta xem KĨ qua cấu trúc của 2 interface này:


public interface IEnumerator
{
    object Current { get; }
    bool MoveNext();
    void Reset();
}

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

Giải thích sơ qua một chút, giả sử IEnumerable là một mảng chứa các số: 11 12 13 14 15

Như vậy IEnumerator đóng vai trò “con trỏ” trong list này, và lúc khởi tạo thì nó đang trỏ vào vị trí index = 0 tức là số 11, đặt tên con trỏ này là numerator. Trong numerator, Current sẽ trỏ tới số 11, hàm MoveNext() trả về true nếu con trỏ vẫn có thể đi tiếp tới các số 12 13 14 15, false nếu đã đi hết mảng.

image.png

Minh họa hoạt động của IEnumerator và IEnumerable

Đào sâu hơn về IEnumerator

Đầu tiên, cho một ví dụ để trực quan hơn:

public IEnumerator GetListInt() {

        yield return 1;

        yield return 2;

        yield return 3;

    int i = 0;

        yield return 4;

        yield return 5;

        yield return 6;

}

public void Example1() {

    IEnumerator test = GetListInt();

    while (test.MoveNext()) {

        Debug.Log(test.Current);

    }
}

Log: 1 2 3 4 5 6

Ở ví dụ trên khi ta gọi MoveNext(), nó sẽ đi từng câu lệnh trong hàm GetListInt() cho tới khi gặp câu lệnh với từ khóa yield return, đẩy giá trị sau yield return (ở ví dụ trên là 1 2 3 4 5 6) vào một biến gọi là IEnumerator là current, đồng thời ghi nhớ vị trí hiện tại và thoát ra ngoài.

Cứ tiếp tục như vậy cho tới khi không MoveNext() được nữa thì thoát ra khỏi vòng lặp.

Ở ví dụ trên có thể xem IEnumerator là con trỏ của một mảng int gồm: 1 2 3 4 5 6

IEnumerable là gì?

Lại ném cho anh em một cái ví dụ:

public IEnumerable GetListInt2() {
        yield return 1;
        yield return 2;
        yield return 3;
        yield return 4;
        yield return 5;
        yield return 6;
}

public void Example2()
{
    IEnumerable ble = GetListInt2();
    IEnumerator tor = ble.GetEnumerator();
    while (tor.MoveNext())
    {
        Debug.Log(tor.Current);
    }
}

Nếu các bạn đọc lại interface của IEnumerable thì có thể thấy hàm GetEnumerator(), hiểu đơn giản nó là một mảng có hỗ trợ sử dụng IEnumerator

“Mình vẫn không hiểu? Giải thích thêm tí đê”


Lại tưởng tượng IEnumerable là một quyển sách, IEnumerator là mảnh giấy đánh dấu trang (đang đọc). Một quyển sách có thể có nhiều mảnh đánh dấu trang, di chuyển một mảnh đánh dấu trang (.MoveNext()) không ảnh hưởng tới quyển sách cũng như các mảnh đánh dấu trang khác.

Việc gọi hàm .GetEnumerator cũng giống như việc bạn đặt thêm một mảnh đánh dấu trang vào trang đầu quyển sách vậy

Các mảnh đánh dấu trang này chỉ được đặt theo chiều đi lên nhé, không khứ hồi.

Tổ hợp IEnumerator, IEnumerable và Foreach

Chắc mọi người đã quá quen với cách sử dụng foreach:

List<int> listInt = new List<int>() { 1, 2, 3, 4, 5 };

foreach (int item in listInt) {
    Debug.Log(item);
}

Thực tế, compiler sẽ ngầm hiểu:

IEnumerator<int> enumerator = listInt.GetEnumerator();

while (enumerator.MoveNext()) {
    var item = enumerator.Current;
    Debug.Log(item);
}

Tất nhiên là compiler sẽ KHÔNG hoàn toàn dịch như trên, mình chỉ ghi lại ý chính phục vụ bài viết.

“Vậy tại sao ở ví dụ đầu lại có hàm sử dụng được với IEnumerator và IEnumerable vậy?”


Compiler đã TỰ tạo một class như List vậy, dùng để xử lý trường hợp iterator method này. Nếu anh em tò mò thì có thể tham khảo cách mà compiler đã xử lý hàm trả về IEnumerator/ IEnumerable ở CSharpInDepth

“Sao không thấy nhắc gì tới hàm Reset của IEnumerator vậy?”


Hiện nay thì mình không thấy ai sử dụng hàm này nữa, cách phổ biến khi muốn reset là tạo lại một IEnumerator bằng cách .GetIEnumerator().

Disclaimer

Bài viết từ 2019 nên chỉ có giá trị tham khảo bạn nhé 😉

Nguồn tham khảo


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí