C# - C Sharp: Indexer
Trong C#, indexer cho phép các thể hiện của lớp hay struct được đánh chỉ mục giống như mảng. Indexer có cú pháp khá tương tự với property, nhưng nó không phải là property, bộ truy cập của indexer cho phép có một hoặc nhiều tham số.
Mục đích của indexer
Giả sử bạn là một giảng viên và bạn muốn thông qua hồ sơ của từng sinh viên để nắm được sự tiến bộ của các sinh viên của bạn. Có hai cách, cách thứ nhất là bạn sử dụng một số thao tác thích hợp để thiết lập (set) và lấy (get) một bản ghi cụ thể bạn muốn, điều này có thể làm bạn mất thời gian và đôi chút buồn tẻ; cách thứ hai là bạn tạo ra một indexer cho các mã sinh viên, điều này sẽ làm cho việc tiếp cận các hồ sơ dễ dàng hơn nhiều. Có được điều này là do indexer sử dụng vị trí chỉ mục của mã sinh viên để xác định vị trí của hồ sơ tương ứng.
Định nghĩa indexer
Indexer là thành phần dữ liệu cho phép bạn truy cập dữ liệu bên trong các đối tượng theo cách tương tự như truy cập mảng. Indexer cung cấp khả năng truy cập nhanh hơn tới dữ liệu trong đối tượng bởi vì nó thực hiện việc đánh chỉ mục cho những dữ liệu đó. Cách để indexer có thể truy cập được vào bên trong từng đối tượng đó chính là thông qua chỉ số của từng đối tượng đó.
Việc thực thi indexer cũng tương tự như thực thi property, chỉ khác ở một điểm đó là việc khai báo một indexer có thể có chứa tham số. Trong C#, indexer còn được biết đến như là mảng thông minh (smart array).
Khai báo indexer
Bạn có thể áp dụng indexer cho lớp, struct hoặc interface, và để khai báo một indexer bạn cần:
· Bổ từ truy cập, nó sẽ quyết định tầm vực của indexer.
· Kiểu trả về của indexer.
· Từ khoá this, dùng để tham chiếu tới đối tượng (thể hiện) hiện thời của lớp hiện thời.
· Cặp ngoặc vuông ([]), trong đó có kiểu dữ liệu và bộ định danh cho chỉ số.
· Cặp ngoặc xoắn ({}), nơi chứa các bộ truy cập set và get.
Cú pháp:
{
get => Tên_mảng[Tham_số];
set => Tên_mảng[Tham_số] = Giá_trị;
}
Ví dụ:
using System; namespace Demo { class Indexer { public string[] empName = new string[2]; public string this[int index] { get => empName[index]; set => empName[index] = value; } static void Main(string[] args) { Indexer objEmp = new Indexer(); objEmp[0] = "Nguyen Thi Phuong Thuy"; objEmp[1] = "Dao Thi Soi"; Console.WriteLine("Ten nhan vien:"); for (int i = 0; i < 2; i++) { Console.Write(objEmp[i] + "\n"); } } } }
Tham số của indexer
Indexer phải có ít nhất một tham số. Tham số dùng để biểu thị chỉ số của đối tượng, tham số sẽ lưu giá trị là vị trí chỉ số nơi đối tượng được gán hoặc được truy cập; điều này tương tự với việc gán hoặc truy cập giá trị trong mảng một chiều. Ngoài ra, nếu indexer có nhiều tham số thì việc truy cập cũng sẽ tương tự như đối với mảng nhiều chiều.
Về vấn đề truy cập indexer, indexer được truy cập thông qua thể hiện của lớp hiện thời kèm với chỉ số đằng sau thể hiện đó.
Thừa kế indexer
Indexer có thể được thừa kế giống như những thành phần khác của lớp, tức là những indexer của lớp cơ sở có thể được thừa kế bởi các lớp dẫn xuất. Ví dụ:
using System; namespace Demo { class Numbers { private int[] num = new int[3]; public int this[int index] { get => num[index]; set => num[index] = value; } } class EvenNumbers : Numbers { public static void Main() { EvenNumbers objEven = new EvenNumbers(); objEven[0] = 0; objEven[1] = 2; objEven[2] = 4; for (int i = 0; i < 3; i++) { Console.WriteLine(objEven[i]); } } } }
Trong ví dụ trên, lớp Numbers tạo một indexer với một đối số kiểu int, lớp EvenNumbers thừa kế lớp Numbers, trong hàm Main() tạo một thể hiện của lớp dẫn xuất EvenNumbers, khi thể hiện này được gán các giá trị tại mỗi vị trí chỉ số thì bộ truy cập set của indexer trong lớp cơ sở Numbers được gọi với mỗi vị trí chỉ số. Vòng lặp for lặp 3 lần và hiển thị các giá trị được gán cho mỗi vị trí chỉ số sử dụng bộ truy cập get. Như vậy thông qua biện pháp thừa kế thì indexer trong lớp cơ sở đã được tái sự dụng trong lớp thừa kế.
Đa hình trong indexer
Indexer còn có thể thực hiện tính đa hình bằng cách ghi đè indexer của lớp cơ sở hoặc bằng cách nạp chồng indexer.
Bằng cách thực thi tính đa hình, lập trình viên cho phép các indexer của lớp dẫn xuất ghi đè indexer của lớp cơ sở. Ngoài ra, trong cùng một lớp ta có thể tạo nhiều indexer, miễn sao những indexer này phải có những dấu hiệu khác nhau; điều này có nghĩa là ta có thể nạp chồng indexer.
Như vậy, tính đa hình cho phép indexer có thể hoạt động với các kiểu dữ liệu khác nhau của C# và tạo đầu ra (output) một cách tuỳ chỉnh.
Ví dụ, đoạn mã dưới đây sẽ cho thấy tính đa hình của indexer với việc ghi đè indexer của lớp cơ sở:
using System; namespace Demo { class Student { string[] studName = new string[2]; public virtual string this[int index] { get => studName[index]; set => studName[index] = value; } } class Result : Student { string[] result = new string[2]; public override string this[int index] { get => base[index]; set =>base[index] = value; } static void Main(string[] args) { Result objResult = new Result(); objResult[0] = "First"; objResult[1] = "Pass"; Student objStudent = new Student(); objStudent[0] = "Luong"; objStudent[1] = "Hoang"; for (int i = 0; i < 2; i++) { Console.WriteLine(objStudent[i] + "\t\t" + objResult[i] + " class"); } } } }
Trong ví dụ trên, lớp Student khai báo một biến mảng và một indexer virtual, lớp Result thừa kế lớp Student và override indexer virtual. Phương thức Main() khai báo một thể hiện của lớp cơ sở Student và một thể hiện của lớp dẫn xuất Result. Khi thể hiện của lớp Student được gán giá trị tại mỗi vị trí chỉ số thì bộ truy cập set của indexer lớp Student được gọi. Khi thể hiện của lớp Result được gán giá trị tại mỗi vị trí chỉ số thì bộ truy cập set của indexer lớp Result được gọi. Ở đây có hiện tượng indexer của lớp dẫn xuất đã ghi đè indexer của lớp cơ sở, cụ thể bộ truy cập set của lớp dẫn xuất Result đã gọi indexer của lớp cơ sở bằng cách sử dụng từ khoá base, và vòng lặp for hiển thị các giá trị tại mỗi vị trí chỉ số bằng cách gọi các bộ truy cập get của các lớp tương ứng. Kết quả của ví dụ được thể hiện ở hình dưới đây.
Indexer nhiều tham số
Indexer bắt buộc phải có tối thiểu một tham số. Ngoài ra, ta có thể tạo indexer với nhiều tham số, khi đó indexer sẽ được truy cập giống như mảng nhiều chiều; những indexer này có thể được dùng để tổ chức một tập các giá trị liên quan, chẳng hạn như nó có thể được dùng để lưu trữ cũng như thay đổi các giá trị đó.
Dưới đây là ví dụ về indexer có hai tham số:
using System; namespace Demo { class Account { string[,] accountDetails = new string[4, 2]; public string this[int pos, int column] { get => accountDetails[pos, column]; set => accountDetails[pos, column] = value; } static void Main(string[] args) { Account objAccount = new Account(); string[] id = new string[3] { "1001", "1002", "1003" }; string[] name = new string[3] { "Long", "Thanh", "Binh" }; int counter = 0; for (int i = 0; i < 3; i++) { for (int j = 0; j < 1; j++) { objAccount[i, j] = id[counter]; objAccount[i, j + 1] = name[counter++]; } } Console.WriteLine("ID Ten"); Console.WriteLine(); for (int i = 0; i < 4; i++) { for (int j = 0; j < 2; j++) { Console.Write(objAccount[i, j] + " "); } Console.WriteLine(); } } } }
Trong ví dụ trên, lớp Account tạo một biến mảng accountDetails gồm 4 hàng và hai cột. Indexer sẽ nhập các giá trị các phần tử của mảng accountDetails. Indexer gồm hai tham số và chúng sẽ định nghĩa các vị trí của các giá trị mà sẽ được lưu trong mảng. Phương thức Main() tạo một thể hiện của lớp Account, thể hiện này được dùng để nhập các giá trị cho mảng accountDetails thông qua vòng lặp for, điều này sẽ gọi đến bộ truy cập set của indexer và set sẽ gán các giá trị cho các phần tử mảng. Vòng lặp for lồng phía dưới sẽ hiển thị ID được lưu trong mảng bằng cách gọi bộ truy cập get.
Sử dụng Indexer trong interface
Trong interface đương nhiên bạn cũng có quyền khai báo indexer, chỉ có lưu ý rằng bạn chỉ được khai báo indexer, trong đó các bộ truy cập của indexer cũng chỉ được là khai báo, không có phần định nghĩa. Mỗi indexer khai báo trong interface cũng mặc định có bổ từ truy cập là public và phải được thực thi trong lớp thực thi interface đó. Điều này sẽ làm tăng khả năng tái sử dụng và cung cấp một cách linh hoạt các interface tùy chỉnh. Ví dụ:
using System; namespace Demo { public interface IDetails { string this[int index] { get; set; } } class Students : IDetails { string[] studentName = new string[3]; int[] studentID = new int[3]; public string this[int index] { get => studentName[index]; set => studentName[index] = value; } static void Main(string[] args) { Students objStudent = new Students(); objStudent[0] = "Phuong"; objStudent[1] = "Hung"; objStudent[2] = "Trinh"; Console.WriteLine("Ten sinh vien:"); for (int i = 0; i < 3; i++) { Console.WriteLine(objStudent[i]); } } } }
Trong ví dụ trên, interface IDetails khai báo một indexer read-write. Lớp Student thực thi interface IDetails và thực thi indexer được định nghĩa trong interface. Phương thức Main() tạo một thể hiện của lớp Students và thông qua bộ truy cập set của indexer nó gán các giá trị cho mảng studentName. Thông qua bộ truy cập get của indexer, vòng lặp sẽ in ra màn hình giá trị của các phần tử mảng studentName.
Sự khác nhau giữa Property và Indexer
- Property được gán một tên duy nhất trong quá trình khai báo, còn Indexer không thể có tên, từ khóa this sẽ thay thế khi khai báo.
- Property được gọi thông qua tên của nó, còn Indexer được gọi thông qua chỉ số của thể hiện được tạo.
- Property có thể được khai báo là tĩnh (dùng từ khóa static), Indexer không thể được khai báo là tĩnh.
- Property luôn luôn được khai báo mà không có tham số, Indexer phải được khai báo với ít nhất một tham số.
- Property không thể được nạp chồng, nhưng Indexer có thể được nạp chồng.
- Việc ghi đè Property được thực hiện bằng cách sử dụng cú pháp: base.Tên_property. Còn việc ghi đè Indexer được thực hiện bằng cách sử dụng cú pháp: base[Danh_sách_tham_số].