Trong .NET, IEnumerable
và IEnumerator
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#.
Mục Lụ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 MyIntergerCollection
và MyIntergerEnumerator
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
IEnumerable
và IEnumerator
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.