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.. Gia
Trang 1// 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);
}
Trang 2Console.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
4. Hi n th c ki u kh -li t-kê (enumerable type) Hi n th c ki u kh -li t-kê (enumerable type) ệ ệ ự ể ự ể ả ệ ả ệ
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 giao diện System.IEnumerable trong kiểu tập hợp của bạn Phương
thức GetEnumerator của giao diện IEnumerable trả về một enumerator—một đối
Trang 3nghĩ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 giao diệ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 Giao diệ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 giao diện IEnumerator.
Bảng 16.1 Các thành viên của giao diện IEnumerator
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
Trang 4public 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 giao diệ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:
// Lớp Team mô tả tập hợp các đối tượng TeamMember Hiện thực giao diện
// IEnumerable để hỗ trợ việc liệt kê các đối tượng TeamMember
public class Team : IEnumerable {
// TeamMemberEnumerator là một lớp private lồng bên trong, cung cấp
// chức năng liệt kê các đối tượng TeamMember trong tập hợp
// Team Vì là lớp lồng bên trong nên TeamMemberEnumerator
// có thể truy xuất các thành viên private của lớp Team
private class TeamMemberEnumerator : IEnumerator {
private Team sourceTeam;
// Giá trị luận lý cho biết Team nằm dưới có thay đổi hay không
private bool teamInvalid = false;
Trang 5// Giá trị nguyên cho biết TeamMember hiện tại (chỉ số
// trong ArrayList) Giá trị ban đầu là -1
private int currentMember = -1;
// Phương thức khởi dựng (nhận một tham chiếu đến Team)
internal TeamMemberEnumerator(Team team) {
this.sourceTeam = team;
sourceTeam.TeamChange +=
new TeamChangedEventHandler(this.TeamChange);
}
// Hiện thực thuộc tính IEnumerator.Current
public object Current {
get {
// Nếu TeamMemberEnumerator đứng trước phần tử đầu tiên
// hoặc sau phần tử cuối cùng thì ném ngoại lệ
if (currentMember == -1 ||
currentMember > (sourceTeam.teamMembers.Count-1)) {
throw new InvalidOperationException();
}
// Nếu không, trả về TeamMember hiện tại
return sourceTeam.teamMembers[currentMember];
}
}
// Hiện thực phương thức IEnumerator.MoveNext
public bool MoveNext() {
// Nếu Team nằm dưới bất hợp lệ, ném ngoại lệ
if (teamInvalid) {
Trang 6
throw new InvalidOperationException("Team modified"); }
// Nếu không, tiến đến TeamMember kế tiếp
currentMember++;
// Trả về false nếu ta dịch qua khỏi TeamMember cuối cùng
if (currentMember > (sourceTeam.teamMembers.Count-1)) { return false;
} else {
return true;
}
}
// Hiện thực phương thức IEnumerator.Reset Phương thức này // reset vị trí của TeamMemberEnumerator về đầu tập hợp Team public void Reset() {
// Nếu Team nằm dưới bất hợp lệ, ném ngoại lệ
if (teamInvalid) {
throw new InvalidOperationException("Team modified"); }
// Dịch con trỏ currentMember về trước phần tử đầu tiên currentMember = -1;
}
// Phương thức thụ lý sự kiện tập hợp Team nằm dưới thay đổi internal void TeamChange(Team t, EventArgs e) {
// Báo hiệu Team nằm dưới hiện đang bất hợp lệ
teamInvalid = true;
}
}
Trang 7// Ủy nhiệm dùng để chỉ định chữ ký mà tất cả
// các phương thức thụ lý sự kiện phải hiện thực
public delegate void TeamChangedEventHandler(Team t, EventArgs e);
// ArrayList dùng để chứa các đối tượng TeamMember
private ArrayList teamMembers;
// Sự kiện dùng để báo cho TeamMemberEnumerator
// biết Team đã thay đổi
public event TeamChangedEventHandler TeamChange;
// Phương thức khởi dựng Team
public Team() {
teamMembers = new ArrayList();
}
// Hiện thực phương thức IEnumerable.GetEnumerator
public IEnumerator GetEnumerator() {
return new TeamMemberEnumerator(this);
}
// Thêm một đối tượng TeamMember vào Team
public void AddMember(TeamMember member) {
teamMembers.Add(member);
if (TeamChange != null) {
TeamChange(this, null);
}
}
}
Nếu lớp tập hợp của bạn chứa nhiều kiểu dữ liệu khác nhau và bạn muốn liệt kê chúng một cách riêng rẽ, việc hiện thực giao diện IEnumerable trên lớp tập hợp này thì vẫn còn thiếu Trong trường hợp này, bạn cần hiện thực một số thuộc tính trả về các thể hiện khác nhau của
Trang 8IEnumerator Ví dụ, nếu lớp Team mô tả cả các thành viên và các máy tính trong đội, bạn có thể hiện thực các thuộc tính này như sau:
// Thuộc tính dùng để liệt kê các thành viên trong đội
public IEnumerator Members {
get {
return new TeamMemberEnumerator(this);
}
}
// Thuộc tính dùng để liệt kê các computer trong đội
public IEnumerator Computers {
get {
return new TeamComputerEnumerator(this);
}
}
Khi đó, bạn có thể sử dụng các enumerator này như sau:
Team team = new Team();
§
foreach(TeamMember in team.Members) {
// Làm gì đó
}
foreach(TeamComputer in team.Computers) {
// Làm gì đó
}
Lệnh foreach cũng hỗ trợ các kiểu có hiện thực một mẫu tương đương với mẫu
được định nghĩa bởi giao diện IEnumerable và IEnumerator, mặc dù kiểu đó không hiện thực các giao diện này Tuy nhiên, mã lệnh của bạn sẽ rõ ràng hơn và dễ hiểu hơn nếu bạn hiện thực giao diện IEnumerable Bạn hãy xem C# Language
[ http://msdn.microsoft com/net/ecma ].
Trang 95. Hi n th c l p kh -h y (disposable class) Hi n th c l p kh -h y (disposable class) ệ ệ ự ớ ự ớ ả ủ ả ủ
Bạn cần tạo một lớp có tham chiếu đến các tài nguyên không-được-quản-lý và
cung cấp một cơ chế để người dùng giải phóng các tài nguyên đó một cách tất định.
Hiện thực giao diện System.IDisposable, và giải phóng các tài nguyên
không-được-quản-lý khi mã client gọi phương thức IDisposable.Dispose.
Một đối tượng không được tham chiếu đến vẫn tồn tại trên vùng nhớ động (heap) và tiêu thụ các tài nguyên cho đến khi bộ thu gom rác (Garbage Collector) giải phóng đối tượng và các
tài nguyên Bộ thu gom rác sẽ tự động giải phóng các tài nguyên được-quản-lý (như bộ nhớ), nhưng nó sẽ không giải phóng các tài nguyên không-được-quản-lý (như file handle và kết nối
cơ sở dữ liệu) được tham chiếu bởi các đối tượng được-quản-lý Nếu một đối tượng chứa các thành viên dữ liệu tham chiếu đến các tài nguyên không-được-quản-lý, đối tượng này phải giải phóng các tài nguyên đó.
Một giải pháp là khai báo một destructor—hay finalizer—cho lớp Trước khi giải phóng phần
bộ nhớ do một thể hiện của lớp sử dụng, bộ thu gom rác sẽ gọi finalizer của đối tượng này Finalizer có thể thực hiện các bước cần thiết để giải phóng các tài nguyên
không-được-quản-lý Vì bộ thu gom rác chỉ sử dụng một tiểu trình để thực thi tất cả các finalizer, việc sử dụng finalizer có thể bất lợi trong quá trình thu gom rác và ảnh hưởng đến hiệu năng của ứng dụng Ngoài ra, bạn không thể kiểm soát khi bộ thực thi giải phóng các tài nguyên không-được-quản-lý vì bạn không thể trực tiếp gọi finalizer của một đối tượng, và bạn chỉ có quyền kiểm soát hạn chế trên các hoạt động của bộ thu gom rác bằng lớp System.GC.
Bằng cách sử dụng finalizer, NET Framework định nghĩa mẫu Dispose như một phương tiện
cung cấp quyền kiểm soát khi bộ thực thi giải phóng các tài nguyên không-được-quản-lý Để
hiện thực mẫu Dispose, lớp phải hiện thực giao diện IDisposable Giao diện này khai báo một phương thức có tên là Dispose; trong đó, bạn phải hiện thực phần mã cần thiết để giải phóng các tài nguyên không-được-quản-lý.
Các thể hiện của các lớp có hiện thực mẫu Dispose được gọi là các đối tượng khả-hủy (disposable object) Khi mã lệnh đã hoàn tất với một đối tượng khả-hủy, nó sẽ gọi phương
thức Dispose của đối tượng để giải phóng các tài nguyên không-được-quản-lý, vẫn dựa vào bộ thu gom rác để giải phóng các tài nguyên được-quản-lý của đối tượng Cần hiểu rằng bộ thực thi không bắt buộc hủy các đối tượng; việc gọi phương thức Dispose là nhiệm vụ của client
Tuy nhiên, vì thư viện lớp NET Framework sử dụng mẫu Dispose rộng khắp nên C# cung cấp
lệnh using để đơn giản hóa việc sử dụng các đối tượng khả-hủy Đoạn mã sau trình bày cấu trúc của lệnh using:
using (FileStream fileStream = new FileStream("SomeFile.txt",
FileMode.Open)) {
// Làm gì đó với đối tượng fileStream
}
Dưới đây là một số điểm cần lưu ý khi hiện thực mẫu Dispose:
Trang 10• Mã client nên có khả năng gọi đi gọi lại phương thức Dispose mà không gây ra các ảnh hưởng bất lợi.
• Trong các ứng dụng hỗ-trợ-đa-tiểu-trình, điều quan trọng là chỉ có một tiểu trình thực thi phương thức Dispose Thông thường, bảo đảm sự đồng bộ tiểu trình là nhiệm vụ của
mã client, mặc dù bạn có thể hiện thực sự đồng bộ bên trong phương thức Dispose.
• Phương thức Dispose không nên ném ngoại lệ.
• Vì phương thức Dispose dọn dẹp tất cả nên không cần gọi finalizer của đối tượng Phương thức Dispose của bạn nên gọi phương thức GC.SuppressFinalize để bảo đảm finalizer không được gọi trong quá trình thu gom rác.
• Hiện thực một finalizer sao cho phương thức Dispose sẽ được nó gọi theo một cơ chế
an toàn trong trường hợp mã client gọi Dispose không đúng Tuy nhiên, nên tránh tham chiếu đến các đối tượng được-quản-lý trong finalizer vì không rõ trạng thái của đối tượng.
• Nếu một lớp khả-hủy thừa kế một lớp khả-hủy khác, phương thức Dispose của lớp con phải gọi phương thức Dispose của lớp cha Gói phần mã của lớp con trong một khối try
và gọi phương thức Dispose của lớp cha trong một mệnh đề finally để bảo đảm việc thực thi.
• Các phương thức và thuộc tính khác của lớp nên ném ngoại lệ
System.ObjectDisposedException nếu mã client thực thi một phương thức trên một đối tượng đã bị hủy.
Lớp DisposeExample dưới đây minh họa một hiện thực phổ biến của mẫu Dispose:
using System;
// Hiện thực giao diện IDisposable
public class DisposeExample : IDisposable {
// Phần tử dữ liệu private dùng để báo hiệu
// đối tượng đã bị hủy hay chưa
bool isDisposed = false;
// Phần tử dữ liệu private dùng để lưu giữ
// handle của tài nguyên không-được-quản-lý
private IntPtr resourceHandle;
// Phương thức khởi dựng
public DisposeExample() {
// Thu lấy tham chiếu đến tài nguyên không-được-quản-lý