CodeGym /Các khóa học /C# SELF /Hợp đồng truy cập theo chỉ số:

Hợp đồng truy cập theo chỉ số: IList<T>

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

1. Giới thiệu

Ok, tưởng tượng nhé: bạn có một collection... và bạn cần lấy nhanh phần tử thứ ba, thứ bảy hoặc, ví dụ, phần tử số 0. Hoặc muốn đổi chỗ chúng cho nhau. Với mảng thì dễ rồi — dùng chỉ số (array[3]). Thế còn collection thì sao? Không phải collection nào cũng truy cập theo chỉ số được đâu!

Đây là lúc interface IList<T> xuất hiện — một hợp đồng đa năng, yêu cầu collection phải hỗ trợ làm việc với phần tử qua chỉ số. Nói ngắn gọn: nếu bạn có collection implement IList<T>, thì cứ thoải mái truy cập phần tử qua chỉ số (giống như mảng) và sửa đổi chúng ngay lập tức.

So sánh vui:
Nhớ lại thẻ thư viện đi: mỗi cuốn sách có số thứ tự trên kệ, bạn luôn có thể đến lấy cuốn "thứ ba" trên kệ. Collection hỗ trợ IList<T> cũng y chang vậy đó.

2. Cấu trúc tổng quan và các method của IList<T>

Interface IList<T> — là "anh hùng" của nhiều collection. Nó mở rộng ICollection<T> (còn cái đó lại mở rộng IEnumerable<T>, cho phép duyệt collection bằng vòng lặp), và thêm cái quan trọng nhất: làm việc với chỉ số.

Sơ đồ kế thừa interface:


IEnumerable<T>
      ▲
      │
ICollection<T>
      ▲
      │
  IList<T>
Kế thừa interface collection trong .NET

Các thành phần chính của interface IList<T>

Thành phần Mục đích
T this[int index] { get; set; }
Lấy hoặc gán phần tử theo chỉ số
int IndexOf(T item)
Tìm chỉ số xuất hiện đầu tiên của phần tử
void Insert(int index, T item)
Chèn phần tử vào vị trí chỉ định
void RemoveAt(int index)
Xoá phần tử theo chỉ số

Các thành phần còn lại như Add, Remove, Clear, Contains, đều đến từ interface ICollection<T>.

Điểm hay: indexer

Điểm "chất" nhất của IList<T> là có indexer. Đây là cú pháp sugar cho phép bạn viết:


var myList = new List<int> { 10, 20, 30 };
int secondValue = myList[1]; // Lấy 20
myList[2] = 42;              // Đổi phần tử thứ ba

3. Những collection implement IList<T>

Trong thư viện chuẩn .NET, nhiều cấu trúc quen thuộc hỗ trợ interface IList<T>. Xem qua mấy cái phổ biến nhất nhé:

Collection Truy cập chỉ số Mô tả
List<T>
Mảng động
T[]
Mảng thường cũng truy cập chỉ số được
BindingList<T>
Dùng cho data binding
ObservableCollection<T>
List có thông báo thay đổi
Collection<T>
Lớp cơ bản cho collection

Lưu ý:

LinkedList<T>
HashSet<T>
KHÔNG implement IList<T>, vì chúng không có truy cập chỉ số nhanh (đúng rồi, LinkedList<T> không có list[5] đâu nhé!).

4. Ví dụ sử dụng IList<T>

Lấy và gán theo chỉ số


using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        IList<string> fruits = new List<string> { "Táo", "Chuối", "Lê" };

        // Lấy phần tử thứ hai
        string fruit = fruits[1];
        Console.WriteLine(fruit); // Chuối

        // Đổi phần tử thứ ba
        fruits[2] = "Cam";
        Console.WriteLine(fruits[2]); // Cam
    }
}

Chèn và xoá theo chỉ số


fruits.Insert(1, "Kiwi");    // Chèn "Kiwi" vào vị trí thứ hai
// List giờ là: "Táo", "Kiwi", "Chuối", "Cam"

fruits.RemoveAt(0);          // Xoá phần tử đầu ("Táo")
// List giờ là: "Kiwi", "Chuối", "Cam"

Tìm chỉ số của phần tử


int index = fruits.IndexOf("Cam"); // Trả về chỉ số (2) hoặc -1 nếu không tìm thấy
if (index != -1)
    Console.WriteLine("Cam nằm ở vị trí: " + index);
else
    Console.WriteLine("Cam không tìm thấy");

5. Đặc điểm implement và lỗi thường gặp

Khi làm việc với IList<T>, dễ dính vài "bẫy", nhất là nếu quên rằng chỉ số bắt đầu từ 0, còn độ dài collection là số phần tử hiện tại.

Ví dụ, thử truy cập phần tử không tồn tại:


Console.WriteLine(fruits[100]); // IndexOutOfRangeException!

Chỉ số trong C# giống như gà mái: bắt đầu từ 0 chứ không phải 1. Nếu list có 4 phần tử, chỉ số lớn nhất là 3.

Cũng nên nhớ, không phải mọi implement IList<T> đều nhanh như nhau. Ví dụ, mảng hoặc List<T> truy cập chỉ số cực nhanh (O(1)), nhưng nếu bạn tự implement collection dựa trên linked list rồi lại gán cho nó IList<T>, thì thao tác này có thể chậm. Tuy nhiên thư viện chuẩn không làm vậy đâu.

Và nữa: nếu bạn dùng mảng như IList<T>, bạn có thể đổi phần tử, nhưng không đổi được kích thước mảng. Các method Add, Remove, Insert v.v. trên mảng sẽ ném ra NotSupportedException.


int[] myArray = { 1, 2, 3 };
IList<int> listView = myArray; // Upcast

listView[0] = 42; // Ok luôn!
listView.Add(99); // Sẽ ném NotSupportedException

6. Ứng dụng thực tế và tại sao cần nó

Trong dự án thực tế, gần như collection nào cũng là thứ gì đó implement IList<T>, vì tiện lợi khi truy cập phần tử theo số thứ tự, sửa đổi, chèn/xoá theo vị trí. Ví dụ:

  • Property interface trong ViewModel WPF hoặc WinForms, nơi bind list UI element.
  • Implement thuật toán sort, tìm kiếm, hoán đổi, cần truy cập theo chỉ số.
  • Module import/export dữ liệu, làm việc với list object động.

Khi phỏng vấn câu hỏi về khác biệt giữa IEnumerable<T>, ICollection<T>IList<T> là kinh điển. Biết mỗi tầng làm gì, bạn sẽ tự tin giải thích cho interviewer tại sao HashSet<T> không implement IList<T> (vì tính duy nhất quan trọng hơn thứ tự và chỉ số!).

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