1. Tại sao collection cần interface?
Nói thật nhé: khi mình code với kiểu cụ thể (List<int>, Dictionary<string, string>), mọi thứ đều dễ hiểu và đơn giản. Nhưng thử tưởng tượng bạn muốn thay List<T> bằng HashSet<T> vì bạn chỉ cần giá trị duy nhất. Hoặc bạn muốn viết một method có thể làm việc với cả hai loại collection này. Làm sao bây giờ?
Cần một điểm vào chung — kiểu như “cổng kết nối đa năng”. Trong C#, cái cổng đó chính là interface. Interface định nghĩa bộ quy tắc (method, property) mà collection phải có. Bất cứ thứ gì implement interface đó — đều hỗ trợ các thao tác đã chỉ định.
Kiểu như sạc điện thoại ấy: nếu có cổng USB-C chuẩn, bạn có thể sạc điện thoại, máy hút bụi hiện đại, thậm chí bàn chải đánh răng điện (miễn là đừng nhầm lẫn trong phòng tối thôi).
Ví dụ thực tế
Giả sử bạn muốn viết một method tính tổng tất cả số trong bất kỳ collection số nào. Đây là cách làm nếu không dùng interface:
// Method này chỉ chạy với mảng:
static int SumArray(int[] array)
{
int sum = 0;
for (int i = 0; i < array.Length; i++)
{
sum += array[i];
}
return sum;
}
// Còn method này - chỉ cho List<int>:
static int SumList(List<int> list)
{
int sum = 0;
for (int i = 0; i < list.Count; i++)
sum += list[i];
return sum;
}
Khá bất tiện! Code thì gần giống nhau. Dùng interface IEnumerable<int> là giải quyết được:
static int SumAll(IEnumerable<int> collection)
{
int sum = 0;
foreach (var number in collection)
sum += number;
return sum;
}
Giờ method này nhận bất kỳ loại collection nào implement IEnumerable<int>: mảng, list, set, v.v.
2. Những interface chính của collection
Trong .NET, collection có vài interface chính. Cùng điểm qua nhanh nhé — chi tiết từng cái sẽ học ở bài sau.
| Interface | Dùng để làm gì | Ví dụ collection implement interface |
|---|---|---|
|
Duyệt phần tử (foreach) | Tất cả: , , mảng |
|
Tập hợp có thể thay đổi (Add, Remove, Count) | , , |
|
Truy cập theo index, thay đổi phần tử | , mảng |
|
Làm việc với cặp "key-value" | , |
|
Tập hợp (phần tử duy nhất) | , |
P.S. Để đơn giản, mình tạm bỏ qua “nhiều tầng” kế thừa giữa các interface này. Ai kế thừa ai — sẽ nói sau nhé.
Minh họa: cây interface của collection
IEnumerable<T>
^
|
ICollection<T>
^ ^
| |
IList<T> ISet<T>
| |
List<T> HashSet<T>
Với dictionary:
IEnumerable<KeyValuePair<K,V>>
^
|
ICollection<KeyValuePair<K,V>>
^
|
IDictionary<K,V>
|
Dictionary<K,V>
3. Interface để làm gì ngoài đời thực?
Linh hoạt
Khi method hoặc class của bạn làm việc không phải với kiểu cụ thể (List<int>), mà là interface (IEnumerable<int>), bạn có thể truyền vào bất kỳ collection nào implement interface đó. Kết quả là chương trình linh hoạt, dễ mở rộng. (Kiểu như “Chỉ iPhone” — giờ thì điện thoại nào có Bluetooth cũng chơi được.)
static void PrintAll(IEnumerable<string> collection)
{
foreach (var item in collection)
Console.WriteLine(item);
}
Giờ có thể in phần tử của bất kỳ list, mảng, set, thậm chí kết quả truy vấn LINQ!
Thống nhất
Nếu bạn tự viết kiểu collection riêng (ví dụ một collection rất đặc biệt cho một bài toán nào đó), hãy implement các interface cần thiết — collection của bạn sẽ chạy được với code của người khác, dù họ không biết collection của bạn là gì. Đó là lý do .NET hay nói: “hãy lập trình ở mức interface, không phải implementation cụ thể”.
Tương thích với công cụ chuẩn
Nhờ hỗ trợ interface chuẩn, collection của bạn có thể dùng với LINQ, sort, serialize và nhiều tính năng khác của .NET.
4. List<T> — vừa là list, vừa là collection, vừa là enumerable
Vài lời về enumerable
IEnumerable<T> — là interface quan trọng nhất và cơ bản nhất của collection trong .NET. Nó chỉ định đúng một method — GetEnumerator(), trả về object enumerator để duyệt qua collection. Nhờ nó bạn mới có thể viết:
foreach (var name in names)
{
Console.WriteLine(name);
}
Ở đây names có thể là list, set, thậm chí mảng. foreach là cú pháp sugar, thực chất gọi method GetEnumerator() bên dưới.
Interface IEnumerable (không có <T>) — là bản cũ, không generic. Giờ gần như luôn dùng bản generic.
Cú pháp khai báo class List
Xem thử khai báo class List<T>, nếu “vén màn ảo thuật” một chút:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, ...
Nghĩa là object List<T> có thể dùng như:
- List với truy cập index tự do (IList<T>)
- Collection có thể thêm/xóa phần tử (ICollection<T>)
- Tập hợp phần tử có thể duyệt qua (IEnumerable<T>)
Ví dụ:
List<string> names = new List<string> { "Anna", "Boris", "Vasya" };
// Dùng như IEnumerable - duyệt vòng lặp
IEnumerable<string> asEnumerable = names;
foreach (var n in asEnumerable) Console.WriteLine(n);
// Dùng như ICollection - lấy Count:
ICollection<string> asCollection = names;
int count = asCollection.Count;
// Dùng như IList - truy cập theo index:
IList<string> asList = names;
string firstItem = asList[0];
5. Ví dụ ứng dụng
Giả sử bạn có một app đơn giản quản lý user và role của họ (mình đã bắt đầu làm ở bài trước):
// Class user
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
Bạn có thể lưu user trong bất kỳ collection nào implement IEnumerable<User>:
List<User> listUsers = new List<User> { new User { Name = "Anna", Age = 20 } };
HashSet<User> setUsers = new HashSet<User> { new User { Name = "Boris", Age = 25 } };
// Method in ra tên tất cả user
static void PrintUserNames(IEnumerable<User> users)
{
foreach (var user in users)
Console.WriteLine(user.Name);
}
// Dùng với nhiều loại collection:
PrintUserNames(listUsers);
PrintUserNames(setUsers);
Giờ app linh hoạt hơn nhiều — không cần overload custom gì cả!
6. Lỗi phổ biến và bẫy thường gặp
Một lỗi rất hay gặp — dùng kiểu quá cụ thể khi chỉ cần interface:
// Không nên (bị dính chặt vào implementation)
void DoSomethingWithList(List<int> numbers) { ... }
// Tốt hơn (method chạy với mọi collection)
void DoSomethingWithNumbers(IEnumerable<int> numbers) { ... }
Một bẫy nữa — quên rằng interface chỉ cho phép thao tác đã mô tả rõ trong nó. Ví dụ, IEnumerable<T> không cho biết số phần tử (Count), muốn vậy phải dùng ICollection<T> — hoặc tự đếm bằng tay.
GO TO FULL VERSION