IEnumerable và IEnumerator


Trong bài trước, chúng ta đã có đề cập đến hai interface trong C#, đó là IComparable và IComparer. Hôm nay, chúng ta sẽ tiếp tục tìm hiểu về hai interface khác rất thường gặp nhưng có lẽ bạn không để ý là IEnumerable và IEnumerator

Mục đích của interface IEnumerable là cho phép chúng ta có thể sử dụng từ khóa foreach trên đối tượng của class cài đặt interface này. Một ví dụ về class chúng ta thường dùng có cài đặt IEnumerable là List. Trong trường hợp chúng ta sử dụng Generics List<> thì interface được cài đặt là IEnumerable<>.Giả sử chúng ta có khai báo về class Person như sau:

class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
}

Trong hàm Main, chúng ta tạo ra một danh sách các đối tượng Person và thực hiện duyệt qua các đối tượng đó bằng câu lệnh foreach như sau

List<Person> personList = new List<Person>();
personList.Add(new Person { Age = 10, Name = "John" });
personList.Add(new Person { Age = 5, Name = "Anna" });
personList.Add(new Person { Age = 8, Name = "Kevin" });

foreach (var item in personList)
{
    Console.WriteLine(item.Name + ":" + item.Age);
}

Giả sử chúng ta không muốn sử dụng List<> làm danh sách chứa các đối tượng Person mà chúng ta muốn tự mình tạo ra một lớp riêng gọi là PersonCollection (có thể bạn thắc mắc tại sao phải tạo ra lớp riêng? Đơn giản bởi vì có thể chúng ta muốn cài đặt một phương thức đặt biệt nào đó mà bản thân List<> không cung cấp). Nội dung của class PersonCollection là như sau:

class PersonCollection
{
    private List<Person> personList;
    public void Add(Person person)
    {
        personList.Add(person);
    }
}

Trong class PersonCollection, chúng ta có một dữ liệu private kiểu List<Person> dùng để lưu trữ danh sách các đối tượng Person. Trong thực tế, bạn có thể chọn các lưu trữ khác do bạn tự định nghĩa như Stack, Queue, LinkedList, Tree… miễn sao bạn có thể lưu lại các đối tượng mà bạn sẽ thao tác trong PersonCollection. Ngoài ra chúng ta cũng có 1 phương thức là Add dùng để thêm một đối tượng vào danh sách private của class.

Khi đã có class PersonCollection, chúng ta thử thay thế đoạn code trong hàm Main bằng đoạn code sử dụng class vừa mới tạo thử xem:

PersonCollection personList = new PersonCollection();
personList.Add(new Person { Age = 10, Name = "John" });
personList.Add(new Person { Age = 5, Name = "Anna" });
personList.Add(new Person { Age = 8, Name = "Kevin" });

foreach (var item in personList)
{
    Console.WriteLine(item.Name + ":" + item.Age);
}
Console.ReadKey(true);

Tuy nhiên, khi bạn biên dịch đoạn code này thì bạn sẽ nhận được thông báo lỗi: foreach statement cannot operate on variables of type ‘Example.PersonCollection’ because ‘Example.PersonCollection’ does not contain a public definition for ‘GetEnumerator’ 

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:

class PersonCollection : IEnumerable<Person>
{
    private List<Person> personList;
    public PersonCollection()
    {
        personList = new List<Person>();
    }
    public void Add(Person person)
    {
        personList.Add(person);
    }

    public IEnumerator<Person> GetEnumerator()
    {
    }
	System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
	{
   		 throw new NotImplementedException();
	}
}

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. Nếu có thời gian thì mình sẽ tiếp tục post các bài về interface trong .NET để các bạn cùng tham khảo. Happy coding and see you next time….

Tác giả: xuanchien

Tran Xuan Chien. Japan Advanced Institute of Science and Technology - Japan. Senior Developer - NUS Technology.

7 thoughts on “IEnumerable và IEnumerator”

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s