Trong C#, khi bạn làm việc với một tập hợp các đối tượng trong
một mảng, một danh sách… thì yêu cầu thường gặp là sắp xếp danh sách đó. Nếu như
sử dụng Generics trong C# thì bạn chỉ có thể sắp xếp được các kiểu dữ liệu cơ
bản như int, float, string… nhờ sử dụng phương thức Sort. Còn những kiểu dữ liệu
đặc biệt do người dùng tự định nghĩa (ví dụ là các class như Student, Person,
Car…) thì bạn cần phải chỉ định rõ cách sắp xếp cho kiểu dữ liệu đó.
Để cho các bạn dễ hiểu thì chúng ta sẽ xét một ví dụ sau: Tạo
một danh sách các số nguyên và sắp xếp danh sách đó theo thứ tự tăng dần. Yêu
cầu sử dụng List<> để chứa các số nguyên đó và sử dụng phương thức Sort có
sẵn trong List<>. Đoạn code theo yêu cầu bên trên sẽ là như sau:
1 | List< int > intList = new List< int >(); |
7 | foreach ( int number in intList) |
8 | Console.WriteLine(number); |
Như các bạn thấy, chúng ta đã tận dụng phương thức Sort có sẵn
trong List<> để sắp xếp các số nguyên này. Bởi vì int là một kiểu dữ liệu
cơ bản nên trình biên dịch hoàn toàn biết cách sắp xếp thứ tự các phần tử từ bé
đến lớn.
Ví dụ tiếp theo có phần phức tạp hơn, đó là thay vì danh sách
các số nguyên thì chúng ta sẽ làm việc với danh sách các đối tượng của lớp
Person như sau:
3 | public int Age{ get ; set ;} |
4 | public string Name{ get ; set ;} |
Việc tạo và thêm phần tử cho danh sách các đối tượng kiểu
Person này cũng không có gì là khó khăn.
01 | List<Person> personList = new List<Person>(); |
02 | personList.Add( new Person{Age=10, Name= "John" }); |
03 | personList.Add( new Person{Age=15, Name= "Ann" }); |
04 | personList.Add( new Person{Age=8, Name= "Kevin" }); |
06 | foreach (var item in personList) |
08 | Console.WriteLine(item.Name + ":" + item.Age); |
Nhưng lúc này chúng ta không thể gọi phương thức Sort() trên
personList bởi vì trình biên dịch sẽ không hiểu là cách sắp xếp đối tượng kiểu
Person này là như thế nào (cho 2 đối tượng kiểu Person là A và B, sẽ sắp xếp A
trước B hay B trước A?). Nếu bạn không tin thì có thể thử, bạn sẽ nhận được
InvalidOperationException với thông điệp “Failed to
compare two elements in the array”.
Phương thức Sort() của List<> sẽ chỉ có thể thực hiện
được trên những class có cài đặt interface IComparable. Nói
cách khác, các class này phải có một phương thức chỉ rõ cách so sánh hai đối
tượng của class đó là như thế nào. IComparable có một phương
thức duy nhất là CompareTo. Tham số nhận vào là một kiểu object hoặc trong
trường hợp bạn sử dụng interface IComparable<> thì tham
số truyền vào là kiểu dữ liệu của lớp cài đặt interface đó. Như vậy, lớp Person
của chúng ta lúc này sẽ là:
1 | class Person : IComparable<Person> |
3 | public int Age { get ; set ;} |
4 | public string Name{ get ; set ;} |
5 | public int CompareTo(Person other) |
7 | return this .Age.CompareTo(other.Age); |
Phương thức CompareTo sẽ dựa vào giá trị trả về để biết được
thứ tự của hai đối tượng khi đem ra so sánh. Giá trị trả về nhỏ hơn 0 nghĩa là
đối tượng hiện tại sẽ nhỏ hơn (hay đứng trước) đối tượng other. Giá trị trả về
là 0 thì hai đối tượng bằng nhau, còn nếu giá trị trả về lớn hơn 0 thì đối tượng
hiện tại lớn hơn (hay đứng sau) đối tượng other. Ở trên, thay vì phải dùng các
câu lệnh if..else để trả về giá trị theo quy định thì chúng ta đã tận dụng lại
phương thức CompareTo có sẵn trong các kiểu dữ liệu cơ bản (thuộc tính Age thuộc
kiểu Integer). Hiện tại, chúng ta đang cài đặt cho lớp Person này sẽ sắp xếp
theo thuộc tính Age. Nếu như cách so sánh phức tạp hơn, chúng ta chỉ cần cài đặt
nó trong phương thức CompareTo, miễn sao giá trị trả về phản ánh đúng thứ tự mà
chúng ta yêu cầu.
Sau khi đã chỉnh sửa lớp Person như trên thì ta đã có thể sử
dụng được phương thức Sort của List<> để sắp xếp các đối tượng kiểu
Person. Kết quả xuất ra của chúng ta là như sau:
Tuy nhiên, cài đặt IComparable có một hạn chế
đó là chúng ta chỉ có thể quy định cho lớp Person này 1 cách sắp xếp duy nhất.
Giả sử chúng ta muốn cùng lúc vừa có thể sắp xếp danh sách personList theo tên,
vừa có thể sắp xếp theo tuổi thì phải làm sao? Khi đó chúng ta cần đến sự trợ
giúp của một interface khác: IComparer.
Khác với IComparable được cài đặt ngay trên
đối tượng mà bạn muốn sắp xếp thì IComparer như là một bộ so
sánh riêng. Khi cần sắp xếp với bộ sắp xếp nào thì ta chỉ cần gắn nó với bộ so
sánh đó là được (Hãy tưởng tượng bạn đang có 10 thùng hàng và có 2 loại máy sắp
xếp, nếu cho 10 thùng hàng này qua máy 1 thì các thùng hàng sẽ được sắp xếp theo
kích thước, còn nếu cho 10 thùng hàng này qua máy 2 thì các thùng hàng sẽ được
sắp xếp theo khối lượng. Máy 1 và 2 ở đây đóng vai tròng giống như các Comparer,
mỗi máy sẽ có cách sắp xếp riêng mà khi cần sử dụng thì gọi tới nó).
Chúng ta sẽ bắt đầu với việc cài đặt một Bộ so sánh cho phép so
sánh hai đối tượng kiểu Person theo thuộc tính Age và một Bộ so sánh theo thuộc
tính Name
1 | class MyAgeComparer : IComparer<Person> |
3 | public int Compare(Person x, Person y) |
5 | return x.Age.CompareTo(y.Age); |
1 | class MyNameComparer : IComparer<Person> |
3 | public int Compare(Person x, Person y) |
5 | return x.Name.CompareTo(y.Name); |
Tương tự như phương thức CompareTo của interface
IComparable, phương thức Compare trong interface
IComparer cũng dựa vào kết quả trả về để biết thứ tự của hai
đối tượng. Lưu ý rằng interface IComparable được cài đặt trực tiếp ngay trên lớp
các đối tượng trong danh sách, còn IComparer thì được cài đặt
trên một lớp riêng (Vì thế nó sẽ có hai tham số trong phương thức Compare).
Đã có hai Comparer như trên, khi chúng ta muốn sử dụng bộ so
sánh nào khi sắp xếp với phương thức Sort thì chỉ việc chỉ định đối tượng của
lớp Comparer đó. Ví dụ như với danh sách personList đã tạo ra, chúng ta sẽ sắp
xếp danh sách này theo tuổi như sau:
1 | personList.Sort( new MyAgeComparer()); |
Còn nếu bạn muốn sắp xếp theo tên thì sao? Rất dễ dàng, chỉ cần
thay đối tượng tương ứng trong tham số của phương thức Sort
1 | personList.Sort( new MyNameComparer()); |
Làm theo cách này, bạn có thể cài đặt nhiều cách sắp xếp khác
nhau trên một kiểu dữ liệu. Khi nào cần cách sắp xếp nào, ta chỉ việc gọi cách
sắp xếp đó.
Tổng kết: Chúng ta đã tìm hiểu cách sử dụng hai interface
IComparable và IComparer khi cài đặt nó trên
một ví dụ thực tế, trong bài kế tiếp, chúng ta sẽ cùng nhau tìm hiểu về hai
interface khác là IEnumerable và IEnumerator.
See you next time!
Không có nhận xét nào:
Đăng nhận xét