NET Frameworkhỗtrợviệc COM-client sửdụng thành phần .NET. Khi COM-client tạo một 
đối tượng .NET, CLR sẽ tạo một đối tượng được-quản-lý và một COM Callable Wrapper
(CCW) bọc lấy đối tượng này. COM-client sẽtương tác với đối tượng thông qua CCW. CLR
chỉtạo một CCWcho một đối tượng được-quản-lý, bất chấp có bao nhiêu COM-client đang 
sửdụng nó. 
Các kiểu cần được truy xuất bởi COM-client phải thỏa mãn các yêu cầu sau: 
• Các kiểu được-quản-lý (lớp, giao diện, cấu trúc, hoặc kiểu liệt kê) phải được khai báo 
là public. 
• Nếu COM-client cần tạo đối tượng, nó phải có một phương thức khởi dựng mặc định 
public. COMkhông hỗtrợcác phương thức khởi dựng có chứa thông số. 
• Các thành viên của kiểu cần được truy xuất phải là các thành viên public. COM-client 
không truy xuất được các thành viên private, protected, internal, và static. 
Ngoài ra, bạn nên tuân theo các kinh nghiệm sau: 
• Không nên tạo các quan hệthừa kếgiữa các lớp, vì các quan hệnày sẽkhông khảkiến 
đối với COM-client (mặc dù .NETgiảlập quan hệnày bằng cách khai báo một giao 
diện lớp cơsởdùng chung). 
• Các lớp mà bạn trưng ra nên hiện thực một giao diện. Với mục đích kiểm soát phiên 
bản, bạn có thểsửdụng đặc tính System.Runtime.InteropServices.GuidAttribute để
chỉ định GUIDsẽ được gán cho giao diện. 
• Nên tạo tên mạnh cho assembly để nó có thể được cài đặt vào GAC và được dùng 
chung cho nhiều client. 
478 
Chương 15: Khảnăng liên tác mã lệnh không-được-quản-lý 
Đểtạo một đối tượng .NET, COM-client cần một thưviện kiểu (file .tlb). File thưviện kiểu có 
thể được tạo từmột assembly bằng tiện ích dòng lệnh Tlbexp.exe. Ví dụ: 
tlbexp ManagedLibrary.dll 
Một khi đã tạo ra thưviện kiểu, bạn có thểtham chiếu nó từmột công cụphát triển không-được-quản-lý. Với Visual Basic 6, bạn tham chiếu file .tlbtừhộp thoại Project | Reference. 
Trong Visual C++ 6, bạn có thểsửdụng lệnh #import đểnhập các định nghĩa kiểu từthư
viện kiểu.
              
                                            
                                
            
 
            
                 53 trang
53 trang | 
Chia sẻ: oanh_nt | Lượt xem: 1607 | Lượt tải: 2 
              
            Bạn đang xem trước 20 trang nội dung tài liệu Các giải pháp lập trình C# phần 10, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
477 
Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý 
 BeginInit(); 
// 
// axMaskEdBox1 
// 
this.axMaskEdBox1.Location = new System.Drawing.Point(16, 12); 
this.axMaskEdBox1.Name = "axMaskEdBox1"; 
this.axMaskEdBox1.OcxState = ((System.Windows.Forms.AxHost.State) 
 (resources.GetObject("axMaskEdBox1.OcxState"))); 
this.axMaskEdBox1.Size = new System.Drawing.Size(112, 20); 
this.axMaskEdBox1.TabIndex = 0; 
this.Controls.Add(this.axMaskEdBox1); 
Chú ý rằng, các thuộc tính tùy biến của điều kiểm ActiveX không được áp dụng trực tiếp 
thông qua các lệnh thiết lập thuộc tính. Thay vào đó, chúng sẽ được thiết lập theo nhóm khi 
thuộc tính OcxState đã được thiết lập. Tuy nhiên, mã lệnh của bạn có thể sử dụng các thuộc 
tính này một cách trực tiếp. 
15.10 Tạo thành phần .NET dùng cho COM-client 
 Bạn cần tạo một thành phần .NET sao cho một COM-client có thể gọi nó. 
 Tạo một assembly theo các chỉ dẫn trong mục này. Tạo một thư viện kiểu cho 
assembly này bằng tiện ích dòng lệnh Type Library Exporter (Tlbexp.exe). 
.NET Framework hỗ trợ việc COM-client sử dụng thành phần .NET. Khi COM-client tạo một 
đối tượng .NET, CLR sẽ tạo một đối tượng được-quản-lý và một COM Callable Wrapper 
(CCW) bọc lấy đối tượng này. COM-client sẽ tương tác với đối tượng thông qua CCW. CLR 
chỉ tạo một CCW cho một đối tượng được-quản-lý, bất chấp có bao nhiêu COM-client đang 
sử dụng nó. 
Các kiểu cần được truy xuất bởi COM-client phải thỏa mãn các yêu cầu sau: 
• Các kiểu được-quản-lý (lớp, giao diện, cấu trúc, hoặc kiểu liệt kê) phải được khai báo 
là public. 
• Nếu COM-client cần tạo đối tượng, nó phải có một phương thức khởi dựng mặc định 
public. COM không hỗ trợ các phương thức khởi dựng có chứa thông số. 
• Các thành viên của kiểu cần được truy xuất phải là các thành viên public. COM-client 
không truy xuất được các thành viên private, protected, internal, và static. 
Ngoài ra, bạn nên tuân theo các kinh nghiệm sau: 
• Không nên tạo các quan hệ thừa kế giữa các lớp, vì các quan hệ này sẽ không khả kiến 
đối với COM-client (mặc dù .NET giả lập quan hệ này bằng cách khai báo một giao 
diện lớp cơ sở dùng chung). 
• Các lớp mà bạn trưng ra nên hiện thực một giao diện. Với mục đích kiểm soát phiên 
bản, bạn có thể sử dụng đặc tính System.Runtime.InteropServices.GuidAttribute để 
chỉ định GUID sẽ được gán cho giao diện. 
• Nên tạo tên mạnh cho assembly để nó có thể được cài đặt vào GAC và được dùng 
chung cho nhiều client. 
478 
Chương 15: Khả năng liên tác mã lệnh không-được-quản-lý 
Để tạo một đối tượng .NET, COM-client cần một thư viện kiểu (file .tlb). File thư viện kiểu có 
thể được tạo từ một assembly bằng tiện ích dòng lệnh Tlbexp.exe. Ví dụ: 
tlbexp ManagedLibrary.dll 
Một khi đã tạo ra thư viện kiểu, bạn có thể tham chiếu nó từ một công cụ phát triển không-
được-quản-lý. Với Visual Basic 6, bạn tham chiếu file .tlb từ hộp thoại Project | Reference. 
Trong Visual C++ 6, bạn có thể sử dụng lệnh #import để nhập các định nghĩa kiểu từ thư 
viện kiểu. 
479 
16 
Chương 16: CÁC GIAO DIỆN VÀ MẪU THÔNG DỤNG 
480 
481 
Chương 16: Các giao diện và mẫu thông dụng 
hương này trình bày cách hiện thực các mẫu (pattern) sẽ được sử dụng thường xuyên 
trong quá trình phát triển các ứng dụng Microsoft .NET Framework. Một số mẫu 
được chuẩn hóa bằng các giao diện được định nghĩa trong thư viện lớp .NET 
Framework. Một số khác thì ít cứng nhắc hơn, nhưng vẫn yêu cầu bạn thực hiện các cách tiếp 
cận cụ thể để thiết kế và hiện thực các kiểu của bạn. Các mục trong chương này mô tả cách: 
 Tạo các kiểu khả-tuần-tự-hóa để bạn có thể dễ dàng lưu trữ vào đĩa, gửi qua mạng, 
hoặc truyển bằng trị qua các biên miền ứng dụng (mục 16.1). 
 Cung cấp một cơ chế dùng để tạo bản sao đầy đủ và chính xác của đối tượng (mục 
16.2). 
 Hiện thực các kiểu sao cho dễ dàng so sánh và sắp xếp (mục 16.3). 
 Hỗ trợ việc liệt kê các phần tử trong các tập hợp tùy biến (mục 16.4). 
 Bảo đảm rằng một kiểu có sử dụng các tài nguyên không-được-quản-lý sẽ giải phóng 
các tài nguyên đó khi không còn cần đến chúng nữa (mục 16.5). 
 Hiển thị dạng chuỗi của các đối tượng biến đổi dựa trên format specifier (mục 16.6). 
 Hiện thực các kiểu đối số sự kiện và ngoại lệ tùy biến (bạn sẽ thường xuyên sử dụng 
chúng trong quá trình phát triển ứng dụng) (mục 16.7 và 16.8). 
 Hiện thực các mẫu thiết kế thông dụng Singleton và Observer bằng các tính năng có 
sẵn của C# và thư viện lớp .NET Framework (mục 16.9 và 16.10). 
16.1 Hiện thực kiểu khả-tuần-tự-hóa (serializable type) 
 Bạn cần hiện thực một kiểu tùy biến khả-tuần-tự-hóa, cho phép bạn: 
• Lưu trữ các thể hiện của kiểu vào kho lưu trữ (file hay cơ sở dữ liệu). 
• Chuyển các thể hiện của kiểu qua mạng. 
• Truyền các thể hiện của kiểu “bằng trị” qua các biên miền ứng dụng. 
 Đối với việc tuần tự hóa các kiểu đơn giản, hãy áp dụng đặc tính 
System.SerializableAttribute vào khai báo kiểu. Đối với các kiểu phức tạp hơn, 
hoặc để kiểm soát nội dung và cấu trúc của dữ liệu được-tuần-tự-hóa, hãy hiện 
thực giao diện System.Runtime.Serialization.ISerializable. 
Mục 2.12 đã trình bày cách tuần tự hóa và giải tuần tự hóa một đối tượng bằng các lớp 
formatter (được cấp cùng với thư viện lớp .NET Framework). Tuy nhiên, theo mặc định thì 
các kiểu không là khả-tuần-tự-hóa. Để hiện thực một kiểu tùy biến là khả-tuần-tự-hóa, bạn 
phải áp dụng đặc tính SerializableAttribute vào khai báo kiểu. Khi tất cả các trường dữ 
liệu trong kiểu đều là khả-tuần-tự-hóa, việc áp dụng SerializableAttribute là tất cả những 
gì cần làm để khiến cho kiểu tùy biến của bạn là khả-tuần-tự-hóa. Nếu bạn hiện thực một lớp 
tùy biến dẫn xuất từ một lớp cơ sở, lớp cơ sở cũng phải là khả-tuần-tự-hóa. 
Mỗi lớp formatter chứa logic cần thiết để tuần tự hóa các kiểu được gắn với đặc tính 
SerializableAttribute và sẽ tuần tự hóa tất cả các trường public, protected, và private. 
Đoạn mã dưới đây trình bày các khai báo kiểu và khai báo trường của một lớp khả-tuần-tự-
hóa có tên là Employee. 
C 
482 
Chương 16: Các giao diện và mẫu thông dụng 
using System; 
[Serializable] 
public class Employee { 
 private string name; 
 private int age; 
 private string address; 
 § 
} 
 Các lớp dẫn xuất từ một kiểu khả-tuần-tự-hóa không thừa kế đặc tính 
SerializableAttribute. Để khiến cho các kiểu dẫn xuất là khả-tuần-tự-hóa, bạn 
phải khai báo chúng là khả-tuần-tự-hóa bằng cách áp dụng đặc tính 
SerializableAttribute. 
Bạn có thể ngăn việc tuần tự hóa một trường nào đó bằng cách áp dụng đặc tính 
System.NonSerializedAttribute cho trường này. Bạn nên ngăn việc tuần tự hóa đối với các 
trường sau: 
• Chứa các kiểu dữ liệu không-khả-tuần-tự-hóa. 
• Chứa các giá trị có thể không hợp lệ khi đối tượng được giải tuần tự hóa, ví dụ: kết nối 
cơ sở dữ liệu, địa chỉ bộ nhớ, ID của tiểu trình, và handle của tài nguyên không-được-
quản-lý. 
• Chứa các thông tin nhạy cảm hay riêng tư, ví dụ: mật khẩu, khóa mật hóa, và các chi 
tiết riêng về người hay tổ chức. 
• Chứa các dữ liệu dễ dàng tái tạo hay thu lấy được từ các nguồn khác—đặc biệt khi dữ 
liệu lớn. 
Nếu ngăn việc tuần tự hóa một số trường, bạn phải hiện thực kiểu sao cho bù lại việc những 
dữ liệu nào đó sẽ không hiện diện khi một đối tượng được giải tuần tự hóa. Đáng tiếc, bạn 
không thể tạo hay thu lấy các trường dữ liệu bị mất trong một phương thức khởi dựng vì 
formatter không gọi phương thức khởi dựng trong quá trình giải tuần tự hóa đối tượng. Giải 
pháp thông thường nhất là hiện thực mẫu “Lazy Initialization”, trong đó kiểu của bạn sẽ tạo 
hay thu lấy dữ liệu ngay lần đầu tiên cần đến. 
Đoạn mã dưới đây trình bày một phiên bản đã được chỉnh sửa của lớp Employee với đặc tính 
NonSerializedAttribute được áp dụng cho trường address, nghĩa là formatter sẽ không tuần 
tự hóa giá trị của trường này. Lớp Employee hiện thực các thuộc tính công khai dùng để truy 
xuất các thành viên dữ liệu riêng, là nơi thuận tiện để hiện thực “Lazy Initialization” cho 
trường address. 
using System; 
[Serializable] 
public class Employee { 
 private string name; 
 private int age; 
 [NonSerialized] 
 private string address; 
483 
Chương 16: Các giao diện và mẫu thông dụng 
 // Phương thức khởi dựng đơn giản. 
 public Employee(string name, int age, string address) { 
 this.name = name; 
 this.age = age; 
 this.address = address; 
 } 
 // Thuộc tính công khai dùng để truy xuất tên của nhân viên. 
 public string Name { 
 get { return name; } 
 set { name = value; } 
 } 
 // Thuộc tính công khai dùng để truy xuất tuổi của nhân viên. 
 public int Age { 
 get { return age; } 
 set { age = value; } 
 } 
 // Thuộc tính công khai dùng để truy xuất địa chỉ của nhân viên. 
 // Sử dụng "Lazy Initialization" để thiết lập địa chỉ vì 
 // đối tượng được-giải-tuần-tự-hóa sẽ không có giá trị địa chỉ. 
 public string Address { 
 get { 
 if (address == null) { 
 // Nạp địa chỉ từ kho lưu trữ. 
 } 
 return address; 
 } 
 set { 
 address = value; 
 } 
 } 
} 
Đối với phần lớn các kiểu tùy biến, việc sử dụng đặc tính SerializableAttribute và 
NonSerializedAttribute sẽ đáp ứng đủ nhu cầu tuần tự hóa của bạn. Nếu cần kiểm soát quá 
trình tuần tự hóa, bạn cần hiện thực giao diện ISerializable. Các lớp formatter sử dụng logic 
khác nhau khi tuần tự hóa và giải tuần tự hóa thể hiện của các kiểu có hiện thực 
ISerializable. Để hiện thực đúng ISerializable, bạn phải: 
• Khai báo rằng kiểu của bạn có hiện thực giao diện ISerializable. 
• Áp dụng đặc tính SerializableAttribute vào khai báo kiểu như vừa được mô tả; 
không sử dụng NonSerializedAttribute vì nó sẽ không có tác dụng. 
• Hiện thực phương thức ISerializable.GetObjectData (được sử dụng trong quá trình 
tuần tự hóa), phương thức này nhận các kiểu đối số sau: 
▪ System.Runtime.Serialization.SerializationInfo 
▪ System.Runtime.Serialization.StreamingContext 
• Hiện thực một phương thức khởi dựng không công khai (được sử dụng trong quá trình 
giải tuần tự hóa), phương thức này nhận cùng đối số như phương thức GetObjectData. 
Nhớ rằng, nếu bạn có ý định dẫn xuất một số lớp từ lớp khả-tuần-tự-hóa thì phương 
thức khởi dựng này phải là protected. 
484 
Chương 16: Các giao diện và mẫu thông dụng 
• Nếu bạn tạo một lớp khả-tuần-tự-hóa từ một lớp cơ sở cũng có hiện thực 
ISerializable, thì phương thức GetObjectData và phương thức khởi dựng (dùng để 
giải tuần tự hóa) của bạn phải gọi các phương thức tương đương trong lớp cha. 
Trong quá trình tuần tự hóa, formatter sẽ gọi phương thức GetObjectData và truyền cho nó 
các tham chiếu SerializationInfo và StreamingContext. 
• Bạn phải đổ dữ liệu cần tuần tự hóa vào đối tượng SerializationInfo. Lớp 
SerializationInfo cung cấp phương thức AddValue dùng để thêm dữ liệu. Với mỗi lần 
gọi AddValue, bạn phải chỉ định tên dữ liệu (tên này sẽ được sử dụng trong quá trình 
giải tuần tự hóa để thu lấy dữ liệu). Phương thức AddValue có đến 16 phiên bản nạp 
chồng, cho phép bạn thêm nhiều kiểu dữ liệu khác nhau vào đối tượng 
SerializationInfo. 
• Đối tượng StreamingContext cung cấp các thông tin về chủ định và đích của dữ liệu 
được-tuần-tự-hóa, cho phép bạn chọn tuần tự hóa dữ liệu nào. Ví dụ, bạn có thể cần 
tuần tự hóa dữ liệu riêng nếu nó được dành cho một miền ứng dụng khác trong cùng 
tiến trình, nhưng không cần nếu dữ liệu sẽ được ghi ra file. 
Trong quá trình giải tuần tự hóa, formatter sẽ gọi phương thức khởi dựng việc giải tuần tự 
hóa, lại truyền cho nó các tham chiếu SerializationInfo và StreamingContext. 
• Kiểu của bạn phải trích dữ liệu đã-được-tuần-tự-hóa từ đối tượng SerializationInfo 
bằng một trong các phương thức SerializationInfo.Get*, ví dụ: GetString, GetInt32, 
hay GetBoolean. 
• Đối tượng StreamingContext cung cấp các thông tin về nguồn gốc của dữ liệu đã-được-
tuần-tự-hóa, phản ánh logic mà bạn đã hiện thực cho việc tuần tự hóa. 
 Trong quá trình tuần tự hóa chuẩn, formatter không sử dụng khả năng của đối 
tượng StreamingContext để cho biết các chi tiết về nguồn gốc, đích, và chủ định 
của dữ liệu được-tuần-tự-hóa. Tuy nhiên, nếu muốn thực hiện quá trình tuần tự 
hóa tùy biến, bạn có thể cấu hình đối tượng StreamingContext của formatter trước 
khi bắt đầu quá trình tuần tự hóa và giải tuần tự hóa. Tham khảo tài liệu .NET 
Framework SDK để có thêm thông tin về lớp StreamingContext. 
Ví dụ dưới đây trình bày phiên bản đã được chỉnh sửa của lớp Employee, có hiện thực giao 
diện ISerializable. Trong phiên bản này, lớp Employee không tuần tự hóa trường address 
nếu đối tượng StreamingContext chỉ định rằng đích của dữ liệu được-tuần-tự-hóa là file. 
Phương thức Main sẽ giải thích việc tuần tự hóa và giải tuần tự hóa của một đối tượng 
Employee. 
using System; 
using System.Runtime.Serialization; 
[Serializable] 
public class Employee : ISerializable { 
 private string name; 
 private int age; 
 private string address; 
 // Phương thức khởi dựng đơn giản. 
 public Employee(string name, int age, string address) { 
485 
Chương 16: Các giao diện và mẫu thông dụng 
 this.name = name; 
 this.age = age; 
 this.address = address; 
 } 
 // Phương thức khởi dựng dùng để kích hoạt formatter thực hiện việc 
 // giải tuần tự hóa một đối tượng Employee. Bạn nên khai báo 
 // phương thức khởi dựng này là private, hay ít nhất cũng là 
 // protected để bảo đảm nó không bị gọi quá mức cần thiết. 
 private Employee(SerializationInfo info, StreamingContext context) { 
 // Trích xuất tên và tuổi của Employee (sẽ luôn hiện diện 
 // trong dữ liệu đã-được-tuần-tự-hóa bất chấp giá trị 
 // của StreamingContext). 
 name = info.GetString("Name"); 
 age = info.GetInt32("Age"); 
 // Thực hiện trích xuất địa chỉ của Employee 
 // (thất bại nếu không có). 
 try { 
 address = info.GetString("Address"); 
 } catch (SerializationException) { 
 address = null; 
 } 
 } 
 // Các thuộc tính Name, Age, và Address (đã trình bày ở trên). 
 § 
 // Được khai báo bởi giao diện ISerializable, phương thức 
 // GetObjectData cung cấp cơ chế để formatter thu lấy 
 // dữ liệu sẽ-được-tuần-tự-hóa. 
 public void GetObjectData(SerializationInfo inf, 
 StreamingContext con){ 
 // Luôn tuần tự hóa tên và tuổi của Employee. 
 inf.AddValue("Name", name); 
 inf.AddValue("Age", age); 
 // Không tuần tự hóa địa chỉ của Employee nếu StreamingContext 
 // cho biết rằng dữ liệu được-tuần-tự-hóa sẽ được ghi ra file. 
 if ((con.State & StreamingContextStates.File) == 0) { 
 inf.AddValue("Address", address); 
 } 
 } 
 // Chép đè Object.ToString để trả về chuỗi mô tả Employee. 
 public override string ToString() { 
 StringBuilder str = new StringBuilder(); 
 str.AppendFormat("Name: {0}\n\r", Name); 
 str.AppendFormat("Age: {0}\n\r", Age); 
 str.AppendFormat("Address: {0}\n\r", Address); 
 return str.ToString(); 
 } 
 public static void Main(string[] args) { 
486 
Chương 16: Các giao diện và mẫu thông dụng 
 // Tạo một đối tượng Employee mô tả Phuong. 
 Employee phuong = new Employee("Phuong", 23, "HCM"); 
 // Hiển thị Phuong. 
 Console.WriteLine(phuong); 
 // Tuần tự hóa Phuong với đích là một miền ứng dụng khác. 
 // Địa chỉ của Phuong sẽ được tuần tự hóa. 
 Stream str = File.Create("phuong.bin"); 
 BinaryFormatter bf = new BinaryFormatter(); 
 bf.Context = 
 new StreamingContext(StreamingContextStates.CrossAppDomain); 
 bf.Serialize(str, phuong); 
 str.Close(); 
 // Giải tuần tự hóa và hiển thị Phuong. 
 str = File.OpenRead("phuong.bin"); 
 bf = new BinaryFormatter(); 
 phuong = (Employee)bf.Deserialize(str); 
 str.Close(); 
 Console.WriteLine(phuong); 
 // Tuần tự hóa Phuong với đích là file. Trong trường hợp này, 
 // địa chỉ của Phuong sẽ không được tuần tự hóa. 
 str = File.Create("phuong.bin"); 
 bf = new BinaryFormatter(); 
 bf.Context = new StreamingContext(StreamingContextStates.File); 
 bf.Serialize(str, phuong); 
 str.Close(); 
 // Giải tuần tự hóa và hiển thị Phuong. 
 str = File.OpenRead("phuong.bin"); 
 bf = new BinaryFormatter(); 
 phuong = (Employee)bf.Deserialize(str); 
 str.Close(); 
 Console.WriteLine(phuong); 
 Console.ReadLine(); 
 } 
} 
16.2 Hiện thực kiểu khả-sao-chép (cloneable type) 
 Bạn cần tạo một kiểu tùy biến cung cấp một cơ chế đơn giản để lập trình viên tạo 
bản sao cho các thể hiện của kiểu. 
 Hiện thực giao diện System.ICloneable. 
Khi gán một kiểu giá trị sang một kiểu giá trị khác là bạn đã tạo một bản sao của giá trị đó. 
Không có mối liên hệ nào giữa hai giá trị—một thay đổi trên giá trị này sẽ không ảnh hưởng 
đến giá trị kia. Tuy nhiên, khi gán một kiểu tham chiếu sang một kiểu tham chiếu khác (ngoại 
trừ chuỗi—được bộ thực thi xử lý đặc biệt), bạn không tạo một bản sao mới của kiểu tham 
chiếu. Thay vào đó, cả hai kiểu tham chiếu đều chỉ đến cùng một đối tượng, và những thay 
đổi trên giá trị của đối tượng đều được phản ánh trong cả hai tham chiếu. Để tạo một bản sao 
thật của một kiểu tham chiếu, bạn phải “nhái” lại đối tượng mà nó chỉ đến. 
487 
Chương 16: Các giao diện và mẫu thông dụng 
Giao diện ICloneable nhận dạng một kiểu là khả-sao-chép và khai báo phương thức Clone là 
một cơ chế mà thông qua đó, bạn có thể thu lấy bản sao của một đối tượng. Phương thức 
Clone không nhận đối số nào và trả về một System.Object, bất chấp kiểu đang hiện thực là gì. 
Điều này nghĩa là một khi đã sao một đối tượng, bạn phải ép bản sao về đúng kiểu. 
Cách hiện thực phương thức Clone cho một kiểu tùy biến tùy thuộc vào các thành viên dữ liệu 
được khai báo bên trong kiểu. Nếu kiểu tùy biến chỉ chứa các thành viên dữ liệu kiểu giá trị 
(int, byte...) và System.String, bạn có thể hiện thực phương thức Clone bằng cách tạo một 
đối tượng mới và thiết lập các thành viên dữ liệu của nó có giá trị giống như đối tượng hiện 
tại. Lớp Object (tất cả các kiểu đều dẫn xuất từ đây) chứa phương thức MemberwiseClone 
dùng để tự động hóa quá trình này. Ví dụ dưới đây trình bày một lớp đơn giản có tên là 
Employee, chỉ chứa các thành viên chuỗi. Do đó, phương thức Clone dựa vào phương thức 
thừa kế MemberwiseClone để tạo bản sao. 
using System; 
public class Employee : ICloneable { 
 public string Name; 
 public string Title; 
 // Phương thức khởi dựng đơn giản. 
 public Employee(string name, string title) { 
 Name = name; 
 Title = title; 
 } 
 // Tạo một bản sao bằng phương thức Object.MemberwiseClone 
 // vì lớp Employee chỉ chứa các tham chiếu chuỗi. 
 public object Clone() { 
 return MemberwiseClone(); 
 } 
} 
Nếu kiểu tùy biến của bạn có chứa các thành viên dữ liệu kiểu tham chiếu, bạn phải quyết 
định xem phương thức Clone của bạn sẽ thực hiện một bản sao cạn (shallow copy) hay một 
bản sao sâu (deep copy). Bản sao cạn nghĩa là bất kỳ thành viên dữ liệu kiểu tham chiếu nào 
trong bản sao đều sẽ chỉ đến đối tượng giống như thành viên dữ liệu kiểu tham chiếu tương 
ứng trong đối tượng gốc. Bản sao sâu nghĩa là bạn phải sao toàn bộ đồ thị đối tượng (object 
graph) để các thành viên dữ liệu kiểu tham chiếu của bản sao chỉ đến các bản sao (độc lập về 
mặt vật lý) của các đối tượng được tham chiếu bởi đối tượng gốc. 
Dễ dàng hiện thực một bản sao cạn—sử dụng phương thức MemberwiseClone vừa được mô tả. 
Tuy nhiên, một bản sao sâu thường là cái mà lập trình viên mong đợi khi lần đầu tiên sao một 
đối tượng—nhưng hiếm khi là cái họ lấy. Điều này đặc biệt đúng đối với các lớp tập hợp 
trong không gian tên System.Collections, tất cả đều hiện thực bản sao cạn trong các phương 
thức Clone của chúng. Mặc dù sẽ có ích nếu các tập hợp này hiện thực bản sao sâu, có hai lý 
do chính để các kiểu (đặc biệt là các lớp tập hợp) không hiện thực bản sao sâu: 
• Việc tạo bản sao của một đồ thị đối tượng lớn sẽ tốn nhiều bộ nhớ và thời gian xử lý. 
• Các tập hợp thông thường có thể chứa các đồ thị đối tượng sâu và rộng, bao gồm bất kỳ 
kiểu đối tượng nào. Việc tạo một hiện thực bản sao sâu để phục vụ nhiều thứ như thế là 
488 
Chương 16: Các giao diện và mẫu thông dụng 
không khả thi vì một số đối tượng trong tập hợp có thể không phải là khả-sao-chép, và 
một số khác có thể chứa các tham chiếu vòng, khiến quá trình sao chép trở thành một 
vòng lặp vô tận. 
Đối với các tập hợp kiểu mạnh, trong đó bản chất của các phần tử được hiểu và được kiểm 
soát thì một bản sao sâu có thể là một tính năng rất hữu ích. Ví dụ, System.Xml.XmlNode hiện 
thực một bản sao sâu trong phương thức Clone, điều này cho phép bạn tạo đúng bản sao của 
toàn bộ hệ thống phân cấp đối tượng XML chỉ với một lệnh đơn. 
 Nếu cần sao một đối tượng không hiện thực ICloneable nhưng lại là khả-tuần-tự-
hóa, bạn có thể tuần tự hóa rồi giải tuần tự hóa đối tượng đó để có được cùng kết 
quả như khi sao chép. Tuy nhiên, quá trình tuần tự hóa có thể không tuần tự hóa 
tất cả các thành viên dữ liệu (như đã được thảo luận trong mục 16.1). Cũng vậy, 
nếu tạo một kiểu khả-tuần-tự-hóa tùy biến, bạn có thể sử dụng quá trình tuần tự 
hóa vừa được mô tả để thực hiện một bản sao sâu bên trong phương thức 
ICloneable.Clone. Để sao một đối tượng khả-tuần-tự-hóa, bạn hãy sử dụng lớp 
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter để tuần tự hóa 
đối tượng này thành một đối tượng System.IO.MemoryStream, và rồi giải tuần tự 
hóa đối tượng này từ System.IO.MemoryStream. 
Lớp Team dưới đây hiện thực phương thức Clone thực hiện một bản sao sâu. Lớp Team chứa 
một tập hợp các đối tượng Employe mô tả một nhóm người. Khi bạn gọi phương thức Clone 
của một đối tượng Team, phương thức này sẽ tạo bản sao của mỗi đối tượng Employee và thêm 
nó vào đối tượng Team được sao. Lớp Team cung cấp một phương thức khởi dựng private để 
đơn giản hóa mã lệnh trong phương thức Clone (sử dụng phương thức khởi dựng là cách 
thông thường để đơn giản hóa quá trình sao chép). 
using System; 
using System.Collections; 
public class Team : ICloneable { 
 public ArrayList TeamMembers = new ArrayList(); 
 public Team() { 
 } 
 // Phương thức khởi dựng private — được phương thức Clone gọi 
 // để tạo một đối tượng Team mới và đổ vào ArrayList của nó 
 // bản sao của các đối tượng Employee từ một ArrayList có trước. 
 private Team(ArrayList members) { 
 foreach (Employee e in members) { 
 TeamMembers.Add(e.Clone()); 
 } 
 } 
 // Thêm một đối tượng Employee vào Team. 
 public void AddMember(Employee member) { 
 TeamMembers.Add(member); 
 } 
 public object Clone() { 
489 
Chương 16: Các giao diện và mẫu thông dụng 
 // Tạo một bản sao sâu của Team bằng cách gọi phương thức 
 // khởi dựng Team và truyền cho nó ArrayList chứa 
 // các thành viên của Team. 
 return new Team(this.TeamMembers); 
 // Lệnh này sẽ tạo một bản sao cạn của Team: 
 // return MemberwiseClone(); 
 } 
} 
16.3 Hiện thực kiểu khả-so-sánh (comparable type) 
 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 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. 
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 giao diện IComparable. Giao diệ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 giao diệ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 giao diện IComparer. Giao diệ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ả giao diệ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 
490 
Chương 16: Các giao diện và mẫu thông dụ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 c
            Các file đính kèm theo tài liệu này:
 cac_giai_phap_lap_trinh_c_sharp_split_10.pdf cac_giai_phap_lap_trinh_c_sharp_split_10.pdf