IEnumerable và IEnumerator trong C#: Giải thích Cặn kẽ và Cách sử dụng Hiệu quả

Trong .NET, IEnumerableIEnumerator là hai interface quan trọng, đặc biệt khi làm việc với các tập hợp dữ liệu. Bài viết này sẽ đi sâu vào khái niệm, cách sử dụng và tầm quan trọng của chúng trong C#.

IEnumerable là gì và dùng như thế nào?

Trong quá trình lập trình C#, việc duyệt qua các phần tử của một danh sách là một thao tác phổ biến. Dưới đây là một ví dụ điển hình:

List<int> listInt = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var count = listInt.Count;
for (var i = 0; i < count; i++)
{
    var item = listInt[i];
    Console.WriteLine("{0}", item);
}

Một cách khác, hiện đại và được ưa chuộng hơn, là sử dụng vòng lặp foreach:

foreach (var item in listInt)
{
    Console.WriteLine("{0}", item);
}

Vòng lặp foreach hoạt động được là nhờ đối tượng mà nó duyệt qua triển khai interface IEnumerable hoặc IEnumerable<T> (phiên bản generic). Hãy xem định nghĩa của hai interface này:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

Phiên bản generic:

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

Nhiều kiểu dữ liệu trong C#, như Dictionary, List, ArrayList, HashSet, và thậm chí cả string, đều triển khai IEnumerable hoặc IEnumerable<T>.

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable, IDictionary, ICollection, IReadOnlyDictionary<TKey, TValue>, IReadOnlyCollection<KeyValuePair<TKey, TValue>>, ISerializable, IDeserializationCallback { //… }
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T> { //… }
public class ArrayList : IList, ICollection, IEnumerable, ICloneable { //… }
public class HashSet<T> : ICollection<T>, IEnumerable<T>, IEnumerable, ISerializable, IDeserializationCallback, ISet<T>, IReadOnlyCollection<T> { //… }
public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable, IComparable<string>, IEnumerable<char>, IEquatable<string> { //… }

IEnumerable cho phép một đối tượng được duyệt qua bằng foreach. Ví dụ:

public class MyIntergerCollection : IEnumerable
{
    private readonly int[] _listInt;
    public MyIntergerCollection(int[] listInt)
    {
        _listInt = listInt;
    }
    public IEnumerator GetEnumerator()
    {
        return new MyIntergerEnumerator(_listInt);
    }
    //Triển khai non-generic interface IEnumerable
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Đoạn code sử dụng MyIntergerCollection:

var customMyCollection = new MyIntergerCollection(listInt.ToArray());
foreach (var item in customMyCollection)
{
    Console.WriteLine(item);
}

Kết quả sẽ tương tự như khi sử dụng for hoặc foreach trực tiếp trên List<int>.

IEnumerator là gì và có tác dụng như nào?

IEnumerable có phương thức GetEnumerator() trả về một đối tượng triển khai interface IEnumerator. Định nghĩa của interface này như sau:

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

public interface IEnumerator<T> : IDisposable, IEnumerator
{
    T Current { get; }
}

Trong đó:

  • Current: Trả về phần tử hiện tại trong quá trình duyệt.
  • MoveNext(): Di chuyển đến phần tử tiếp theo và trả về true nếu thành công, false nếu đã đến cuối danh sách.
  • Reset(): Đặt lại vị trí duyệt về ban đầu.

IEnumerator cho phép bạn kiểm soát cách duyệt các phần tử. Mặc định, .NET duyệt từ đầu đến cuối danh sách. Trong ví dụ trên, chúng ta sử dụng listInt.GetEnumerator() (vì mảng cũng triển khai IEnumerable) để trả về đối tượng IEnumerator tương ứng.

Để tạo một cách duyệt tùy chỉnh, ta có thể tạo một class triển khai IEnumerator:

public class MyIntergerEnumerator : IEnumerator
{
    private readonly int[] _listInt;
    private int _currentIndex = -1;

    public MyIntergerEnumerator(int[] listInt)
    {
        _listInt = listInt;
    }

    public object Current
    {
        get { return _listInt[_currentIndex]; }
    }

    public bool MoveNext()
    {
        _currentIndex++;
        return _currentIndex < _listInt.Length;
    }

    public void Reset()
    {
        _currentIndex = -1;
    }
}

Sau đó, thay đổi class MyIntergerCollection để sử dụng MyIntergerEnumerator:

public class MyIntergerCollection : IEnumerable
{
    private readonly int[] _listInt;
    public MyIntergerCollection(int[] listInt)
    {
        _listInt = listInt;
    }
    public IEnumerator GetEnumerator()
    {
        return new MyIntergerEnumerator(_listInt);
    }

    //Triển khai non-generic interface IEnumerable
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Việc tạo các class MyIntergerCollectionMyIntergerEnumerator cho phép bạn:

  • Tạo ra một kiểu collection mới có thể duyệt bằng foreach.
  • Tùy biến quá trình duyệt.

Ví dụ, ta có thể duyệt từ phần tử cuối cùng đến phần tử đầu tiên bằng cách chỉnh sửa class MyIntergerEnumerator:

public class MyIntergerEnumerator : IEnumerator
{
    private readonly int[] _listInt;
    private int _currentIndex;

    public MyIntergerEnumerator(int[] listInt)
    {
        _listInt = listInt;
        _currentIndex = listInt.Length;
    }

    public object Current
    {
        get { return _listInt[_currentIndex]; }
    }

    public bool MoveNext()
    {
        _currentIndex--;
        return _currentIndex >= 0;
    }

    public void Reset()
    {
        _currentIndex = _listInt.Length;
    }
}

Kết quả sẽ là:

10
9
8
7
6
5
4
3
2
1

Kết luận

IEnumerableIEnumerator là hai interface mạnh mẽ trong C#, cho phép bạn duyệt và tùy biến cách duyệt các tập hợp dữ liệu. Hiểu rõ về chúng giúp bạn viết code linh hoạt và hiệu quả hơn. Việc tùy chỉnh IEnumerator mở ra nhiều khả năng, từ việc duyệt ngược danh sách đến việc lọc và biến đổi dữ liệu trong quá trình duyệt.