Nguyên nhân lỗi này xuất hiện là vì PersonCollection không có phương thức GetEnumerator, hay nói cách khác là PersonCollection không cài đặt interface IEnumerable để lệnh foreach sử dụng. Hãy thử cài đặt interface này cho class PersonCollection:
Interface IEnumerable có 2 phương thức cùng có tên là GetEnumerator, một cái trả về kiểu IEnumerator<> còn kiểu kia trả về IEnumerator. Phương thức đầu tiên là có trong phiên bản mới hơn để hỗ trợ cho các lớp generics, còn phương thức thứ hai chỉ có mục đích tương thích với các phiên bản .NET cũ không có hỗ trợ generics.
Nhưng hãy để ý kiểu trả về của phương thức GetEnumerator, kiểu trả về là IEnumerator<Person>. Có nghĩa là nó đòi hỏi kiểu trả về phải là một đối tượng của lớp có cài đặt interface IEnumerator<Person>. Có vẻ như mọi thứ đang trở nên phức tạp hơn nhỉ. Tuy nhiên điều này là không có gì phức tạp nếu như bạn đã từng làm việc với Iterator trong C++ hoặc Java. Enumerator có ý nghĩa tương tự. Nó là một đối tượng mà sẽ nói cho chúng ta biết được rằng cách duyệt qua từng phần tử là như thế nào, phần tử hiện tại là gì…
Thế nhưng lấy đâu ra một đối tượng thuộc kiểu IEnumerator<Person> bây giờ? Rất đơn giản, chúng ta chỉ cần tạo ra một lớp cài đặt phương thức trong interface IEnumerator<Person> hoặc là cài đặt interface đó trực tiếp trên lớp Person. Hãy thử làm cách đầu tiên trước, đó là tạo ra một lớp riêng cài đặt interface IEnumerator<Person>
class PersonEnumerator : IEnumerator<Person>
{
private List<Person> list;
private int currentIndex = -1;
private Person currentPerson;
public PersonEnumerator(List<Person> _list)
{
list = _list;
}
public Person Current
{
get { return currentPerson; }
}
public void Dispose() { }
object System.Collections.IEnumerator.Current
{
get { throw new NotImplementedException(); }
}
public bool MoveNext()
{
if (++currentIndex >= list.Count)
{
return false;
}
else
{
currentPerson = list[currentIndex];
}
return true;
}
public void Reset()
{
currentIndex = -1;
}
}
Interface IEnumerator<Person> bao gồm 2 phương thức quan trọng: MoveNext, Reset và một Property là Current.
- Property Current trả về phần tử hiện tại đang được duyệt tới trong danh sách.
- MoveNext dùng để đi đến phần tử tiếp theo trong danh sách (hay nói cách khác thay đổi giá trị của Property Current). Phương thức này trả về giá trị true nếu như việc di chuyển đến đối tượng tiếp theo thành công, trả về false nếu thất bại (trong trường hợp đã đến cuối danh sách). Khi MoveNext trả về false thì Current sẽ có giá trị không xác định.
- Reset dùng để đưa con trỏ hiện tại về vị trí ban đầu. Vị trí ban đầu này là vị trí nằm ngày trước phần tử đầu tiên trong danh sách. Phương thức này có thể không cần cài đặt, nó chỉ được dùng để tương thích với các ứng dụng COM.
Phương thức Dispose có mục đích là hủy các tài nguyên sau khi sử dụng, tuy nhiên ta không có gì để hủy cả nên chỉ cần để trống phương thức này.
Ở trên, chúng ta có truyền vào cho constructor của Enumerator một danh sách các phần tử để duyệt. Tham số truyền vào có thể là mảng, danh sách, đối tượng dạng tập hợp… miễn sao phương thức cài đặt tương ứng trong lớp thỏa mãn việc duyệt qua danh sách đó (Bạn hãy thử làm một Enumerator cho phép duyệt từ cuối danh sách đến đầu danh sách trong câu lệnh foreach thử xem ^_^).
Sau khi đã có lớp PersonEnumerator như trên, ta sẽ quay trở lại phương thức GetEnumerator trong lớp Person. Cài đặt phương thức này rất đơn giản như sau
public IEnumerator<Person> GetEnumerator()
{
return new PersonEnumerator(personList);
}
Chắc bạn cũng để ý thấy rằng việc tạo lớp Enumerator như trên là hơi dư thừa vì dữ liệu duy nhất bạn truyền vào là một List, trong khi List này đã có sẵn trong lớp PersonCollection rồi. Do đó, ta có thể cài đặt interface IEnumerator trực tiếp ngay trên lớp PersonCollection luôn. Các phương thức thì cài đặt cũng tương tự như bên Enumerator, chỉ có điều phương thức GetEnumerator() sẽ được sửa lại tương ứng như sau:
public IEnumerator<Person> GetEnumerator()
{
return this;
}
Ở đây chúng ta return chính đối tượng hiện tại vì đối tượng này có cài đặt interface dùng để duyệt qua danh sách. Cách này giúp bạn giảm thiểu việc viết thêm một class mới vào ứng dụng. Tuy nhiên có thể sẽ làm phức tạp thêm ứng dụng của bạn khi muốn thay đổi, mở rộng hoặc chỉnh sửa sau này (Giả sử bạn có 2 cách duyệt danh sách khác nhau và muốn thay đổi nó thì sao?)
Sau khi đã hoàn thành IEnumerable và IEnumerator, bạn sẽ có thể sử dụng được câu lệnh foreach trên đối tượng thuộc lớp PersonCollection.
Tổng kết: Chúng ta đã biết được công dụng của hai interface IEnumerable và IEnumerator cũng như cách sử dụng chúng cho ứng dụng của bạn khi cần thiết.