1.1 Hiện thực kiểu khả-so-sánh (comparable type)
V
V
Bạn cần một cơ chế dùng để so sánh các kiểu tùy biến, cho phép bạn dễ dàng
sắp xếp tập hợp chứa các thể hiện của kiểu này.
#
#
Để cung cấp một cơ chế so sánh chuẩn cho một kiểu, hiện thực giaodiện
System.IComparable. Để hỗ trợ nhiều dạng so sánh, tạo riêng từng kiểu trợ
giúp (helper) vàcác kiểu này hiện thực giaodiện
System.Collections.IComparer.
Nếu muốn sắp xếp kiểu của bạn chỉ theo một thứ tự nào đó (như ID tăng dần, hay tên
theo thứ tự alphabet), bạn nên hiện thực giaodiện IComparable. Giaodiện này
định nghĩa
phương thức CompareTo như sau:
int CompareTo(object obj);
Đối tượng (obj) được truyền cho phương thức phải cùng kiểu với đối tượng đang gọi, nếu
không CompareTo sẽ ném ngoại lệ System.ArgumentException. Giá trị do CompareTo
trả về được tính như sau:
• Nếu đối tượng hiện tại nhỏ hơn obj, trả về một số âm (chẳng hạn, -1).
• Nếu đối tượng hiện tại có cùng giá trị
như obj, trả về zero.
• Nếu đối tượng hiện tại lớn hơn obj, trả về một số dương (chẳng hạn, 1).
Phép so sánh này thực hiện điều gì là tùy thuộc vào kiểu đã hiện thực giaodiện
IComparable. Ví dụ, nếu muốn sắp xếp dựa theo tên, bạn cần thực hiện phép so sánh
chuỗi (String). Tuy nhiên, nếu muốn sắp xếp dựa theo ngày sinh, bạn cần thực hiện phép
so sánh ngày (System.DateTime).
Để hỗ trợ nhiều dạng sắp xếp cho một kiểu cụ thể, bạn phải hiện thực riêng rẽ từng kiểu
trợ giúp vàcác kiểu này hiện thực giaodiện IComparer. Giaodiện này định nghĩa
phương thức Compare như sau:
int Compare(object x, object y);
Kiểu trợ giúp phải đóng gói logic cần thiết để so sánh hai đối tượng và trả về một giá trị
dựa trên logic như sau:
• Nếu x nhỏ h
ơn y, trả về một số âm (chẳng hạn, -1).
• Nếu x có cùng giá trị như y, trả về zero.
• Nếu x lớn hơn y, trả về một số dương (chẳng hạn, 1).
Lớp Newspaper dưới đây hiện thực cả giaodiện IComparable và IComparer. Phương
thức Newspaper.CompareTo thực hiện phép so sánh không phân biệt chữ hoa-thường hai
đối tượng Newspaper dựa trên trường name của chúng. Một lớp private lồng bên trong có
tên là AscendingCirculationComparer hiện thực IComparer và so sánh hai
đối tượng
Newspaper dựa trên trường circulation của chúng. Đối tượng
AscendingCirculationComparer được thu lấy bằng thuộc tính tĩnh
Newspaper.CirculationSorter.
using System;
using System.Collections;
public class Newspaper : IComparable {
private string name;
private int circulation;
private class AscendingCirculationComparer : IComparer {
int IComparer.Compare(object x, object y) {
// Xử lý các tham chiếu null.
// Null được coi như nhỏ hơn bất cứ giá trị nào khác.
if (x == null && y == null) return 0;
else if (x == null) return -1;
else if (y == null) return 1;
// Trường hợp x và y tham chiếu đến cùng một đối tượng.
if (x == y) return 0;
// Bảo đảm x và y đều là các thể hiện của Newspaper.
Newspaper newspaperX = x as Newspaper;
if (newspaperX == null) {
throw new ArgumentException("Invalid object type", "x");
}
Newspaper newspaperY = y as Newspaper;
if (newspaperY == null) {
throw new ArgumentException("Invalid object type", "y");
}
// So sánh circulation. IComparer quy định rằng:
// trả về một số âm nếu x < y
// trả về zero nếu x = y
// trả về một số dương nếu x > y
// Dễ dàng hiện thực logic này bằng phép tính số nguyên.
return newspaperX.circulation - newspaperY.circulation;
}
}
public Newspaper(string name, int circulation) {
this.name = name;
this.circulation = circulation;
}
// Khai báo một thuộc tính chỉ-đọc, trả về một thể hiện của
// AscendingCirculationComparer.
public static IComparer CirculationSorter{
get { return new AscendingCirculationComparer(); }
}
public override string ToString() {
return string.Format("{0}: Circulation = {1}", name,
circulation);
}
// Phương thức CompareTo so sánh hai đối tượng Newspaper dựa trên
// phép so sánh trường name (không phân biệt chữ hoa-thường).
public int CompareTo(object obj) {
// Một đối tượng luôn được coi như lớn hơn null.
if (obj == null) return 1;
// Trường hợp đối tượng kia là một tham chiếu đến đối tượng này.
if (obj == this) return 0;
// Ép đối tượng kia về Newspaper.
Newspaper other = obj as Newspaper;
// Nếu "other" là null, nó không phải là một thể hiện của
// Newspaper. Trong trường hợp này, CompareTo phải ném
// ngoại lệ System.ArgumentException.
if (other == null) {
throw new ArgumentException("Invalid object type", "obj");
} else {
// Tính giá trị trả về bằng cách thực hiện phép so sánh
// trường name (không phân biệt chữ hoa-thường).
// Vì name là chuỗi nên cách dễ nhất là dựa vào khả năng
// so sánh của lớp String (thực hiện phép so sánh chuỗi
// có phân biệt bản địa).
return string.Compare(this.name, other.name, true);
}
}
}
Phương thức Main minh họa phép so sánh và khả năng sắp xếp nhờ có hiện thực giao
diện IComparable và IComparer. Phương thức này sẽ tạo một tập hợp
System.Collections.ArrayList chứa năm đối tượng Newspaper, sau đó sắp xếp ArrayList
hai lần bằng phương thức ArrayList.Sort. Lần đầu, thao tác Sort sử dụng cơ chế so sánh
mặc định của Newspaper (thông qua phương thức IComparable.CompareTo). Lần sau,
thao tác Sort sử dụng đối tượng AscendingCirculationComparer (thông qua phương thức
IComparer.Compare).
public static void Main() {
ArrayList newspapers = new ArrayList();
newspapers.Add(new Newspaper("Tuoi Tre", 125780));
newspapers.Add(new Newspaper("Echip", 55230));
newspapers.Add(new Newspaper("Thanh Nien", 235950));
newspapers.Add(new Newspaper("Phu Nu", 88760));
newspapers.Add(new Newspaper("Tiep Thi", 5670));
Console.WriteLine("Unsorted newspaper list:");
foreach (Newspaper n in newspapers) {
Console.WriteLine(n);
}
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Newspaper list sorted by name (default order):");
newspapers.Sort();
foreach (Newspaper n in newspapers) {
Console.WriteLine(n);
}
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Newspaper list sorted by circulation:");
newspapers.Sort(Newspaper.CirculationSorter);
foreach (Newspaper n in newspapers) {
Console.WriteLine(n);
}
}
Chạy phương thức Main sẽ sinh ra kết quả như sau:
Unsorted newspaper list:
Tuoi Tre: Circulation = 125780
Echip: Circulation = 55230
Thanh Nien: Circulation = 235950
Phu Nu: Circulation = 88760
Tiep Thi: Circulation = 5670
Newspaper list sorted by name (default order):
Echip: Circulation = 55230
Phu Nu: Circulation = 88760
Thanh Nien: Circulation = 235950
Tiep Thi: Circulation = 5670
Tuoi Tre: Circulation = 125780
Newspaper list sorted by circulation:
Tiep Thi: Circulation = 5670
Echip: Circulation = 55230
Phu Nu: Circulation = 88760
Tuoi Tre: Circulation = 125780
Thanh Nien: Circulation = 235950
1.2 Hiện thực kiểu khả-liệt-kê (enumerable type)
V
V
Bạn cần tạo một kiểu tập hợp sao cho nội dung của nó có thể được liệt kê bằng
lệnh foreach.
#
#
Hiện thực giaodiện System.IEnumerable trong kiểu tập hợp của bạn. Phương
thức GetEnumerator của giaodiện IEnumerable trả về một enumerator—một
đối tượng có hiện thực giaodiện System.IEnumerator. Giaodiện IEnumerator
định nghĩa các phương thức sẽ được lệnh foreach sử dụng để kiệt kê tập hợp.
Một bộ chỉ mục bằng số (numerical indexer) cho phép bạn duyệt qua các phần tử của một
tậ
p hợp bằng vòng lặp for. Tuy nhiên, kỹ thuật này không cung cấp mức trừu tượng phù
hợp với các cấu trúc dữ liệu phi tuyến, như cây và tập hợp đa chiều. Lệnh foreach cung
cấp một cơ chế duyệt qua các đối tượng của một tập hợp mà không quan tâm cấu trúc bên
trong của chúng là gì.
Để hỗ trợ ngữ nghĩa foreach, đối tượng chứa tập hợp phải hiện thực giaodiện
System.IEnumerable. Giao di
ện này khai báo một phương thức có tên là GetEnumerator,
phương thức này không nhận đối số và trả về một đối tượng System.IEnumerator:
IEnumerator GetEnumerator();
Đối tượng IEnumerator là đối tượng hỗ trợ việc liệt kê các phần tử của tập hợp. Giaodiện
IEnumerator cung cấp một con chạy chỉ-đọc, chỉ-tiến (read-only, forward-only cursor)
dùng để truy xuất các thành viên của tập hợp nằm dưới. Bảng 16.1 mô tả các thành viên
của giaodiện IEnumerator.
Bảng 16.1 Các thành viên của giaodiện IEnumerator
Thành
viên
Mô tả
Current
Thuộc tính này trả về phần tử dữ liệu hiện tại. Khi
enumerator được tạo ra, Current chỉ đến vị trí đứng trước
phần tử dữ liệu đầu tiên, nghĩa là bạn phải gọi MoveNext
trước khi sử dụng Current. Nếu Current được gọi và
enumerator đang đứng trước phần tử đầu tiên hoặc sau phần
tử cuối cùng trong tập hợp dữ liệu, Current sẽ ném ngoại l
ệ
System.InvalidOperationException.
MoveNext
Phương thức này dịch chuyển enumerator sang phần tử dữ
liệu kế tiếp trong tập hợp; trả về true nếu còn phần tử, trả về
false nếu không còn phần tử. Nếu nguồn dữ liệu nằm dưới
thay đổi trong thời gian sống của enumerator, MoveNext sẽ
ném ngoại lệ InvalidOperationException.
Reset
Phương thức này dịch chuyển enumerator về vị trí đứng
trước phần tử đầu tiên trong tập hợp dữ liệu. Nếu nguồn dữ
liệu nằm dưới thay đổi trong thời gian sống của enumerator,
Reset sẽ ném ngoại lệ InvalidOperationException.
[
Các lớp TeamMember, Team, và TeamMemberEnumerator minh họa việc hiện thực giao
diện IEnumerable và IEnumerator. Lớp TeamMember mô tả một thành viên của một đội:
// Lớp TeamMember mô tả một thành viên trong đội.
public class TeamMember {
public string Name;
public string Title;
// Phương thức khởi dựng đơn giản.
public TeamMember(string name, string title) {
Name = name;
Title = title;
}
// Trả về chuỗi mô tả TeamMember.
public override string ToString() {
return string.Format("{0} ({1})", Name, Title);
}
}
Lớp Team (mô tả một đội) là một tập hợp các đối tượng TeamMember. Lớp này hiện
thực giaodiện IEnumerable và khai báo một lớp có tên là TeamMemberEnumerator để
cung cấp chức năng liệt kê. Thông thường, các lớp tập hợp sẽ trực tiếp hiện thực cả giao
diện IEnumerable và IEnumerator. Tuy nhiên, sử dụng một lớp enumerator riêng biệt là
cách đơn giản nhất để cho phép nhiều enumerator—và nhiều tiểu trình—liệt kê đồng thời
các phần tử
của Team.
Team hiện thực mẫu Observer bằng cách sử dụng các thành viên sự kiện và ủy nhiệm để
báo cho tất cả các đối tượng TeamMemberEnumerator biết Team nằm dưới có thay đổi
hay không (xem mục 16.10 để có thêm thông tin về mẫu Observer). Lớp
TeamMemberEnumerator là một lớp private lồng bên trong nên bạn không thể tạo các thể
hiện của nó, trừ khi thông qua phương thức Team.GetEnumerator. Dưới đây là phần mã
cho lớp Team và TeamMemberEnumerator:
. hiện thực giao diện
System.IComparable. Để hỗ trợ nhiều dạng so sánh, tạo riêng từng kiểu trợ
giúp (helper) và các kiểu này hiện thực giao diện
System.Collections.IComparer thể, bạn phải hiện thực riêng rẽ từng kiểu
trợ giúp và các kiểu này hiện thực giao diện IComparer. Giao diện này định nghĩa
phương thức Compare như sau: