Indexers
Định nghĩa (Definition)
Bộ chỉ mục (Indexer) là một thành phần đặc biệt giống thuộc tính (Property-like) cho phép các đối tượng được lập chỉ mục sử dụng cú pháp dấu ngoặc vuông (obj[index]). Bộ chỉ mục cho phép lớp hoặc struct hoạt động như một mảng ảo (Virtual Array) hoặc từ điển (Dictionary), cung cấp truy cập phần tử trực quan mà không cần phơi bày cấu trúc dữ liệu bên trong.
// Built-in indexer — every List<T> has one
var list = new List<string> { "a", "b", "c" };
string first = list[0]; // Uses List<T>'s indexer
// Custom indexer — your own types can do the same
var buffer = new TemperatureBuffer();
buffer[0] = 22.5;
double monday = buffer[0];
Khái niệm cốt lõi (Core Concepts)
Cú pháp Bộ chỉ mục cơ bản (Basic Indexer Syntax)
Bộ chỉ mục (Indexer) được khai báo với từ khóa this và tham số trong dấu ngoặc vuông. Chúng hỗ trợ bộ truy cập get và set giống như thuộc tính (Property).
public class TemperatureBuffer
{
private readonly double[] _temps = new double[7];
public double this[int day]
{
get
{
if (day < 0 || day >= 7)
throw new ArgumentOutOfRangeException(nameof(day));
return _temps[day];
}
set
{
if (day < 0 || day >= 7)
throw new ArgumentOutOfRangeException(nameof(day));
_temps[day] = value;
}
}
}
// Usage
var buffer = new TemperatureBuffer();
buffer[0] = 22.5; // set
buffer[6] = 18.0; // set
double monday = buffer[0]; // get — 22.5
Bộ chỉ mục cũng có thể sử dụng thân biểu thức (Expression-bodied):
public class SimpleBuffer
{
private readonly int[] _data = new int[10];
public int this[int index]
{
get => _data[index];
set => _data[index] = value;
}
}
Bộ chỉ mục Đa chiều (Multi-Dimensional Indexers)
Bộ chỉ mục có thể chấp nhận nhiều tham số, cho phép các mẫu truy cập dạng ma trận (Matrix-like) hoặc bảng (Table-like):
public class Matrix
{
private readonly int[,] _data = new int[3, 3];
public int this[int row, int col]
{
get => _data[row, col];
set => _data[row, col] = value;
}
}
var matrix = new Matrix();
matrix[0, 0] = 1;
matrix[1, 2] = 42;
int value = matrix[1, 2]; // 42
// String-keyed multi-parameter indexer
public class ConfigSection
{
private readonly Dictionary<(string Section, string Key), string> _values = new();
public string this[string section, string key]
{
get => _values.TryGetValue((section, key), out var v) ? v : "";
set => _values[(section, key)] = value;
}
}
var config = new ConfigSection();
config["database", "host"] = "localhost";
config["database", "port"] = "5432";
Nạp chồng Bộ chỉ mục (Overloading Indexers)
Một lớp có thể có nhiều bộ chỉ mục với các kiểu tham số khác nhau, tương tự như nạp chồng phương thức (Method Overloading):
public class LookupTable
{
private readonly Dictionary<string, int> _byName = new();
private readonly List<(string Name, int Value)> _items = new();
// Index by integer position
public (string Name, int Value) this[int index]
{
get => index >= 0 && index < _items.Count
? _items[index]
: throw new ArgumentOutOfRangeException(nameof(index));
}
// Index by string name
public int this[string name]
{
get => _byName.TryGetValue(name, out var value)
? value
: throw new KeyNotFoundException(name);
}
public void Add(string name, int value)
{
_byName[name] = value;
_items.Add((name, value));
}
}
var table = new LookupTable();
table.Add("width", 100);
table.Add("height", 200);
int w = table["width"]; // String indexer — 100
var first = table[0]; // Int indexer — ("width", 100)
Bộ chỉ mục trong Giao diện (Indexers in Interfaces)
Bộ chỉ mục có thể được khai báo trong giao diện (Interface) và được triển khai bởi các lớp hoặc struct:
public interface INameLookup
{
string this[int id] { get; }
bool Contains(int id);
}
public class EmployeeLookup : INameLookup
{
private readonly Dictionary<int, string> _employees = new();
public string this[int id] =>
_employees.TryGetValue(id, out var name) ? name : "Unknown";
public bool Contains(int id) => _employees.ContainsKey(id);
public void Add(int id, string name) => _employees[id] = name;
}
// Program against the interface
INameLookup lookup = new EmployeeLookup();
string name = lookup[42];
Bộ chỉ mục vs Thuộc tính (Indexers vs Properties)
| Tính năng (Feature) | Thuộc tính (Property) | Bộ chỉ mục (Indexer) |
|---|---|---|
| Được nhận diện bởi | Tên | this + tham số |
| Tham số | Không có | Một hoặc nhiều |
| Tĩnh (Static) | Có thể là static | Không thể là static |
| Nạp chồng (Overloading) | Không (tên duy nhất) | Có (kiểu tham số khác nhau) |
| Mục đích | Lấy/đặt một giá trị đơn | Lấy/đặt các phần tử được lập chỉ mục |
| Cú pháp truy cập | obj.Name | obj[index] |
Chỉ mục và Phạm vi C# 8 (C# 8 Indices and Ranges)
C# 8 giới thiệu các struct Index 和 Range hoạt động với bộ chỉ mục:
int[] arr = { 0, 1, 2, 3, 4, 5 };
// Index from end
int last = arr[^1]; // 5
int second = arr[^2]; // 4
// Range
int[] slice = arr[1..4]; // [1, 2, 3]
int[] last3 = arr[^3..]; // [3, 4, 5]
Các kiểu tùy chỉnh có thể hỗ trợ những tính năng này bằng cách định nghĩa bộ chỉ mục chấp nhận Index và Range:
public class MyCollection
{
private readonly int[] _data = { 10, 20, 30, 40, 50 };
public int this[Index index] => _data[index.GetOffset(_data.Length)];
public int[] this[Range range] => _data[range];
}
Xem Modern C# để biết chi tiết đầy đủ về chỉ mục và phạm vi.
Khi nào sử dụng (When to Use)
| Tình huống (Scenario) | Sử dụng Bộ chỉ mục (Indexer)? |
|---|---|
| Bộ sưu tập tùy chỉnh bao bọc mảng hoặc từ điển bên trong | Có |
| Cấu trúc dữ liệu ma trận hoặc lưới (Grid) | Có (đa chiều) |
| Cấu hình/cài đặt được truy cập bằng khóa chuỗi | Có |
| Lấy/đặt giá trị đơn giản (không có ngữ nghĩa chỉ mục) | Không — sử dụng thuộc tính (Property) |
| Kiểu không mang tính chất chứa (Container) về mặt khái niệm | Không — sử dụng phương thức |
| Logic tra cứu phức tạp với tác dụng phụ (Side Effects) | Không — sử dụng phương thức có tên |
Lỗi thường gặp (Common Pitfalls)
-
Thiếu kiểm tra giới hạn (Missing Bounds Checking) — Bộ chỉ mục âm thầm truy cập chỉ số không hợp lệ gây ra
IndexOutOfRangeExceptionhoặc làm hỏng trạng thái. Luôn xác thực và némArgumentOutOfRangeExceptionvới thông điệp rõ ràng. -
Phơi bày bộ sưu tập có thể thay đổi bên trong (Exposing Internal Mutable Collections) — Bộ chỉ mục trả về tham chiếu đến đối tượng có thể thay đổi bên trong phá vỡ tính đóng gói (Encapsulation). Trả về bản sao hoặc chế độ xem chỉ đọc cho các kiểu tham chiếu.
-
Lạm dụng bộ chỉ mục (Overusing Indexers) — Nếu mẫu truy cập không cảm thấy tự nhiên (như truy cập mảng/từ điển), phương thức có tên sẽ rõ ràng hơn.
config["timeout"]là trực quan;person["age"]là đáng nghi ngờ. -
Bộ chỉ mục chỉ đọc không có tài liệu (Read-only Indexers with No Documentation) — Bỏ qua bộ truy cập
setmà không có tài liệu gây nhầm lẫn cho người dùng mong đợi quyền ghi. Làm rõ ý định.
Điểm chính (Key Takeaways)
- Bộ chỉ mục sử dụng cú pháp
this[parameters]và hoạt động như thuộc tính có tham số (Parameterized Properties). - Chúng lý tưởng cho các kiểu biểu diễn logic bộ sưu tập, bảng hoặc cấu trúc tra cứu.
- Bộ chỉ mục có thể đa chiều, nạp chồng theo kiểu tham số và được khai báo trong giao diện.
- Luôn xác thực giới hạn chỉ mục trong các bộ truy cập
getvàset. - Ưu tiên phương thức có tên khi phép ẩn dụ "truy cập có chỉ mục" không phù hợp với trừu tượng của kiểu.
Câu hỏi Phỏng vấn (Interview Questions)
Q: Bộ chỉ mục (Indexer) trong C# là gì?
Bộ chỉ mục là một thành phần đặc biệt cho phép các thực thể của lớp hoặc struct được truy cập như một mảng sử dụng cú pháp dấu ngoặc vuông (
obj[index]). Nó được định nghĩa vớithis[parameters]và có bộ truy cập get/set giống như thuộc tính.
Q: Bộ chỉ mục có thể được nạp chồng (Overloaded) không?
Có. Một lớp có thể có nhiều bộ chỉ mục miễn là chúng khác nhau về số lượng hoặc kiểu tham số, tương tự như nạp chồng phương thức.
Q: Sự khác biệt giữa bộ chỉ mục (Indexer) và thuộc tính (Property) là gì?
Thuộc tính được nhận diện bởi tên và không có tham số. Bộ chỉ mục được nhận diện bởi
thisvà chấp nhận một hoặc nhiều tham số (chỉ mục). Bộ chỉ mục không thể là static; thuộc tính có thể.
Q: Bạn có thể khai báo bộ chỉ mục trong giao diện (Interface) không?
Có. Bộ chỉ mục giao diện khai báo chữ ký với
thisvà các bộ truy cập không có triển khai. Các lớp triển khai phải cung cấp logic getter và setter thực tế.
Q: Khi nào nên sử dụng bộ chỉ mục thay vì phương thức?
Sử dụng bộ chỉ mục khi kiểu biểu diễn logic một bộ sưu tập hoặc bảng tra cứu và truy cập giống mảng (
obj[key]) cảm thấy tự nhiên. Sử dụng phương thức có tên khi thao tác phức tạp hơn, có tác dụng phụ (Side Effects), hoặc phép ẩn dụ "truy cập có chỉ mục" không phù hợp.