CodeGym /Các khóa học /C# SELF /Vai trò của interface trong collection C#

Vai trò của interface trong collection C#

C# SELF
Mức độ , Bài học
Có sẵn

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
IEnumerable<T>
Duyệt phần tử (foreach) Tất cả:
List<T>
,
HashSet<T>
, mảng
ICollection<T>
Tập hợp có thể thay đổi (Add, Remove, Count)
List<T>
,
HashSet<T>
,
Dictionary<K,V>
IList<T>
Truy cập theo index, thay đổi phần tử
List<T>
, mảng
IDictionary<K,V>
Làm việc với cặp "key-value"
Dictionary<K,V>
,
SortedDictionary<K,V>
ISet<T>
Tập hợp (phần tử duy nhất)
HashSet<T>
,
SortedSet<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>
Cây phân cấp interface cho collection dạng list và set

Với dictionary:


           IEnumerable<KeyValuePair<K,V>>
                      ^
                      |
               ICollection<KeyValuePair<K,V>>
                      ^
                      |
               IDictionary<K,V>
                      |
                Dictionary<K,V>
Cây phân cấp interface cho dictionary

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.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION