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 không đi hết mảng.
Đà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().
Claims
Bài viết từ 2019 nên chỉ có giá trị tham khảo bạn nhé