Chương 3 : Miềnứngdụng ,cơ chếphảnchiếuvàsiêudữliệu Sức mạnh và tính linh hoạt của Microsoft .NET Framework được nâng cao bởi khả năng kiểm tra và thao tác các kiểu vàsiêudữliệu lúc thực thi. Các mục trong chương này sẽ trình bày các khía cạnh thông dụng của miềnứngdụng (application domain), cơ chếphảnchiếu (reflection), vàsiêudữliệu (metadata), bao gồm: Tạo và hủy các miềnứngdụng (mục 3.1 và 3.9). Làm việc với các kiểu và các đối tượng khi sử dụng nhiều miềnứngdụng (mục 3.2, 3.3, 3.4, và 3.8). Làm việc với thông tin Type (mục 3.10 và 3.11). Tạo động các đối tượng và nạp động các assembly lúc thực thi (mục 3.5, 3.6, 3.7, và 3.12). Tạo và kiểm tra các đặc tính tùy biến (các mục 3.13 và 3.14). 1.1 Tạo miềnứngdụng V V Bạn cần tạo một miềnứngdụng mới. # # Sử dụng phương thức tĩnh CreateDomain của lớp System.AppDomain. Dạng thức đơn giản nhất của phương thức CreateDomain nhận một đối số kiểu string chỉ định tên thân thiện cho miềnứngdụng mới. Các dạng thức khác cho phép bạn chỉ định chứng cứ (evidence) và các thiết lập cấu hình cho miềnứngdụng mới. Chứng cứ được chỉ định bằng đố i tượng System.Security.Policy.Evidence; mục 13.11 trình bày các tác động của chứng cứ khi bạn tạo một miềnứng dụng. Các thiết lập cấu hình được chỉ định bằng đối tượng System.AppDomainSetup. Lớp AppDomainSetup chứa các thông tin cấu hình cho một miềnứng dụng. Bảng 3.1 kiệt kê các thuộc tính thường được sử dụng nhất của lớp AppDomainSetup khi tạo các miềnứng dụng. Các thuộc tính này có thể được truy xuất sau khi tạo thông qua các thành viên củ a đối tượng AppDomain, và một số có thể thay đổi lúc thực thi; bạn hãy tham khảo tài liệu .NET Framework SDK về lớp AppDomain để hiểu chi tiết hơn. Bảng 3.1 Các thuộc tính thông dụng của lớp AppDomainSetup Thuộc tính Mô tả ApplicationBase Thư mục mà CRL sẽ xét trong quá trình dò tìm các assembly riêng. Kỹ thuật dò tìm (probing) sẽ được thảo luận trong mục 3.5. Thực tế, ApplicationBase là thư mục gốc cho ứngdụng đang thực thi. Theo mặc định, đây là thư mục chứa assembly. Có thể đọc được thuộc tính này sau khi tạo miềnứngdụng bằng thuộc tính AppDomain.BaseDirectory. ConfigurationFile Tên của file cấu hình, được sử dụng bởi mã đã được nạp vào miềnứng dụng. Có thể đọc được thuộc tính này sau khi tạo miềnứngdụng bằng phương thức AppDomain.GetData với khóa APP_CONFIG_FILE. DisallowPublisherPolicy Quy định phần publisher policy của file cấu hình ứngdụng có được xét đến hay không khi xác định phiên bản của một assembly tên mạnh để nối kết. Publisher policy sẽ được thảo luận trong mục 3.5. PrivateBinPath Danh sách các thư mục cách nhau bởi dấu chấm phẩy mà bộ thực thi sẽ sử dụng khi dò tìm các assembly riêng. Các thư mục này có vị trí tương đối so với thư mục được chỉ định trong ApplicationBase. Có thể đọc được thuộc tính này sau khi tạo miềnứngdụng bằng thuộc tính AppDomain.RelativeSearchPath. Có thể thay đổi thuộc tính này lúc thực thi bằng phương thức AppendPrivatePath và ClearPrivatePath. Ví dụ dưới đây trình bày cách tạo và cấu hình một miềnứng dụng: // Khởi tạo một đối tượng của lớp AppDomainSetup. AppDomainSetup setupInfo = new AppDomainSetup(); // Cấu hình các thông tin cài đặt cho miềnứng dụng. setupInfo.ApplicationBase = @"C:\MyRootDirectory"; setupInfo.ConfigurationFile = "MyApp.config"; setupInfo.PrivateBinPath = "bin;plugins;external"; // Tạo một miềnứngdụng mới (truyền null làm đối số chứng cứ). // Nhớ lưu một tham chiếu đến AppDomain mới vì nó // không thể được thu lấy theo bất kỳ cách nào khác. AppDomain newDomain = AppDomain.CreateDomain( "My New AppDomain", new System.Security.Policy.Evidence(), setupInfo); Í Bạn phải duy trì một tham chiếu đến đối tượng AppDomain vừa tạo bởi vì không có cơ chế nào để liệt kê các miềnứngdụng hiện có từ bên trong mã được-quản-lý. 1.2 Chuyển các đối tượng qua lại các miềnứngdụng V V Bạn cần chuyển các đối tượng qua lại giữa các miềnứngdụng như các đối số hay các giá trị trả về. # # Sử dụng các đối tượng marshal-by-value hay marshal-by-reference. Hệ thống .NET Remoting (sẽ được thảo luận trong chương 12) giúp việc gởi các đối tượng qua lại các miềnứngdụng trở nên dễ dàng. Tuy nhiên, nếu bạn chưa quen với .NET Remoting, kết quả có thể rất khác so với mong đợi. Thực ra, vấn đề gây khó khăn khi dùng nhiều miềnứngdụng là sự tương tác với .NET Remoting và cách th ức luân chuyển đối tượng qua các miềnứng dụng. Tất cả các kiểu dữliệu có thể chia thành ba loại: nonremotable, marshal-by-value (MBV), và marshal-by-reference (MBR). Kiểu nonremotable không thể vượt qua biên miềnứngdụngvà không thể dùng làm các đối số hay các giá trị trả về của các lời gọi trong môi trường liên miềnứng dụng. Kiểu nonremotable sẽ được thảo luận trong mục 3.4. Kiểu MBV là kiểu khả-tuần-tự-hóa. Khi một đối tượng kiểu MBV được chuyển qua một miềnứngdụng khác như là đối số hay giá trị trả về, hệ thống .NET Remoting sẽ tuần tự hóa trạng thái hiện tại của đối tượng, chuyển dữliệu đó sang miềnứngdụng đích, và tạo một bản sao của đối tượng với cùng trạ ng thái như đối tượng gốc. Kết quả là tồn tại bản sao của đối tượng ở cả hai miềnứng dụng. Hai đối tượng này ban đầu giống nhau hoàn toàn, nhưng độc lập nhau, nên việc thay đổi đối tượng này không ảnh hưởng đến đối tượng kia. Dưới đây là ví dụ một kiểu khả-tuần-tự-hóa có tên là Employee, được chuyển qua một miềnứngdụng khác bằng tr ị (xem mục 16.1 để biết cách tạo kiểu khả-tuần-tự- hóa). [System.Serializable] public class Employee { // Hiện thực các thành viên ở đây. § } Kiểu MBR là lớp dẫn xuất từ lớp System.MarshalByRefObject. Khi một đối tượng kiểu MBR được chuyển qua một miềnứngdụng khác như đối số hay giá trị trả về, hệ thống .NET Remoting sẽ tạo một đối tượng proxy cho đối tượng MBV cần chuyển trong miềnứngdụng đích. Đối tượng đại diện thực hiện các hành vi hoàn toàn giống với đối t ượng MBR mà nó đại diện. Thực ra, khi thực hiện một hành vi trên đối tượng đại diện, hệ thống .NET Remoting thực hiện ngầm việc chuyển lời gọi và các đối số cần thiết đến miềnứngdụng nguồn, và tại đó thực hiện lời gọi hàm trên đối tượng MBR gốc. Kết quả được trả về thông qua đối tượng đại di ện. Dưới đây là một phiên bản khác của lớp Employee, được chuyển qua một miềnứngdụng khác bằng tham chiếu thay vì bằng trị (xem mục 12.7 để biết chi tiết về cách tạo kiểu MBR). public class Employee : System.MarshalByRefObject { // Hiện thực các thành viên ở đây. § } 1.3 Tránh nạp các assembly không cần thiết vào miềnứngdụng V V Bạn cần chuyển một tham chiếu đối tượng qua lại giữa các miềnứngdụng khác nhau; tuy nhiên, bạn không muốn CLR nạp siêudữliệu mô tả kiểu của đối tượng vào các miềnứngdụng trung gian. # # Đóng gói tham chiếu đối tượng trong một System.Runtime.Remoting.ObjectHandle và khi cần truy xuất đối tượng thì khôi phục lại. Khi bạn truyền một đối tượng marshal-by-value (MBV) qua các miềnứng dụng, bộ thực thi sẽ tạo một thể hiện mới của đối tượng này trong miềnứngdụng đích. Điều này có nghĩa là bộ thực thi phải nạp assembly chứa siêudữliệu mô tả kiể u của đối tượng vào các miềnứng dụng. Do đó, việc truyền các tham chiếu MBV qua các miềnứngdụng trung gian sẽ dẫn đến việc bộ thực thi nạp các assembly không cần thiết vào các miềnứngdụng này. Một khi đã được nạp thì các assembly thừa này sẽ không được giải phóng khỏi miềnứngdụng nếu không giải phóng cả miềnứngdụng chứa chúng (xem mục 3.9). Lớp ObjectHandle cho phép bạn đóng gói tham chiếu đối tượng để truyền qua các miềnứngdụng mà bộ thực thi không phải nạp thêm assembly. Khi đối tượng này đến miềnứngdụng đích, bạn có thể khôi phục tham chiếu đối tượng, bộ thực thi sẽ nạp các assembly cần thiết và cho phép bạn truy xuất đến đối tượng như bình thường. Để đóng gói một đối tượng (ví dụ System.Data.DataSet), bạn có thể thực hiện như sau: // Tạo một DataSet mới. System.Data.DataSet data1 = new System.Data.DataSet(); // Cấu hình/thêm dữliệu cho DataSet. § // Đóng gói DataSet. System.Runtime.Remoting.ObjectHandle objHandle = new System.Runtime.Remoting.ObjectHandle(data1); Để khôi phục một đối tượng, sử dụng phương thức ObjectHandle.Unwrap và ép kiểu trả về cho phù hợp, ví dụ: // Khôi phục DataSet từ ObjectHandle. System.Data.DataSet data2 = (System.Data.DataSet)objHandle.Unwrap(); 1.4 Tạo kiểu không thể vượt qua biên miềnứngdụng V V Bạn cần tạo một kiểu dữliệu sao cho các thể hiện của kiểu này không thể được truy xuất từ mã lệnh ở các miềnứngdụng khác. # # Phải chắc chắn kiểu dữliệu thuộc dạng nonremotable, tức là không thể tuần tự hóa cũng như không dẫn xuất từ lớp MarshalByRefObject. Đôi khi bạn muốn kiểu dữliệu nào đó chỉ được giới hạn truy xuất trong phạm vi của miềnứng dụng. Để tạo kiểu dữliệu dạng nonremotable, phải chắc rằng kiểu này không phải là khả -tuần-tự-hóa và cũng không dẫn xuất (trực tiếp hay gián tiếp) từ lớp MarshalByRefObject. Những điều kiện này sẽ đảm bảo rằng trạng thái của đối tượng không thể được truy xuất từ các miềnứngdụng khác (các đối tượng này không thể được sử dụng làm đối số hay giá trị trả về trong các lời gọi phương thức liên miềnứng dụng). Điề u kiện kiểu dữliệu không phải là khả-tuần-tự-hóa được thực hiện dễ dàng do một lớp không thừa kế khả năng tuần tự hóa từ lớp cha của nó. Để bảo đảm một kiểu không phải là khả-tuần-tự-hóa, bạn phải chắc chắn rằng đặc tính System.SerializableAttribute không được áp dụng khi khai báo kiểu. Bạn cần lưu ý khi đảm bảo một lớp không đượ c truyền bằng tham chiếu. Nhiều lớp trong thư viện lớp .NET dẫn xuất trực tiếp hay gián tiếp từ MarshalByRefObject; bạn phải cẩn thận không dẫn xuất lớp của bạn từ các lớp này. Những lớp cơ sở thông dụng dẫn xuất từ MarshalByRefObject bao gồm: System.ComponentModel.Component, System.IO.Stream, System.IO.TextReader, System.IO.TextWriter, System.NET.WebRequest, và System.Net. WebResponse (xem tài liệu .NET Framework SDK để có danh sách đầy đủ các lớp dẫn xuất từ MarshalByRefObject). 1.5 Nạp assembly vào miềnứngdụng hiện hành V V Bạn cần nạp một assembly vào miềnứngdụng lúc thực thi. # # Sử dụng phương thức tĩnh Load hay LoadFrom của lớp System.Reflection.Assembly. Bộ thực thi tự động nạp các assembly mà assembly của bạn tham chiếu đến lúc biên dịch. Tuy nhiên, bạn cũng có thể chỉ thị cho bộ thực thi nạp assembly. Các phương thức Load và LoadFrom đều thực hiện một công việc là nạp một assembly vào miềnứngdụng hiện hành, và cả hai đều trả về một đối tượng Assembly mô tả assembly vừa đượ c nạp. Sự khác biệt giữa hai phương thức là danh sách các đối số được cung cấp để nhận dạng assembly cần nạp, và cách thức bộ thực thi định vị assembly này. Phương thức Load cung cấp nhiều dạng thức cho phép chỉ định assembly cần nạp, bạn có thể sử dụng một trong những dạng sau: • Một string chứa tên đầy đủ hay tên riêng phần để nhận dạng assembly. • M ột System.Reflection.AssemblyName mô tả chi tiết về assembly. • Một mảng byte chứa dữliệu cấu thành assembly. Thông thường, tên của assembly được sử dụng để nạp assembly. Tên đầy đủ của một assembly bao gồm: tên, phiên bản, bản địa, và token khóa công khai, được phân cách bởi dấu phẩy (ví dụ: System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken= b77a5c561934e089). Để chỉ định một assembly không có tên mạnh, sử dụng PublicKeyToken=null. Bạn cũng có thể sử dụng tên ngắn để nhận dạ ng một assembly nhưng ít nhất phải cung cấp tên của assembly (không có phần mở rộng). Đoạn mã dưới đây trình bày các cách sử dụng phương thức Load: // Nạp assembly System.Data dùng tên đầy đủ. string name1 = "System.Data,Version=1.0.5000.0," + "Culture=neutral,PublicKeyToken=b77a5c561934e089"; Assembly a1 = Assembly.Load(name1); // Nạp assembly System.Xml dùng AssemblyName. AssemblyName name2 = new AssemblyName(); name2.Name = "System.Xml"; name2.Version = new Version(1,0,5000,0); name2.CultureInfo = new CultureInfo(""); name2.SetPublicKeyToken( new byte[] {0xb7,0x7a,0x5c,0x56,0x19,0x34,0xe0,0x89}); Assembly a2 = Assembly.Load(name2); // Nạp assembly SomeAssembly dùng tên ngắn. Assembly a3 = Assembly.Load("SomeAssembly"); Khi phương thức Load được gọi, bộ thực thi thực hiện quá trình định vị và nạp assembly. Dưới đây sẽ tóm tắt quá trình này; bạn tham khảo tài liệu .NET Framework SDK để biết thêm chi tiết. 1. Nếu bạn chỉ định assembly tên mạnh, phương thức Load sẽ áp dụng version policy (chính sách phiên bản) và publisher policy (chính sách nhà phát hành) để cho phép khả năng “chuyển tiếp” (redirect) đến một phiên bản assembly khác. Version policy được chỉ định trong file cấu hình máy tính hay ứngdụng của bạn bằng phần tử <bindingRedirect>. Publisher policy được chỉ định trong các assembly đặc biệt được cài đặt bên trong GAC (Global Assembly Cache). 1. Một khi đã xác định đúng phiên bản của assembly cần sử dụng, bộ thực thi sẽ cố gắng nạp các assembly tên mạnh từ GAC. 2. Nếu assembly không có tên mạnh hoặc không được tìm thấy trong GAC, bộ thực thi sẽ tìm phần tử <codeBase> trong các file cấu hình máy tính vàứng dụng. Phần tử <codeBase> ánh xạ tên của assembly thành một file hay một URL. Nếu assembly có tên mạnh, <codeBase> có thể chỉ đến bất kỳ vị trí nào kể cả các URL dựa-trên- Internet; nếu không, <codeBase> phải chỉ đến một thư mục có vị trí tương đối so với thư mục ứngdụ ng. Nếu assembly không tồn tại trong vị trí được chỉ định, phương thức Load sẽ ném ngoại lệ System.IO.FileNotFoundException. 3. Nếu không có phần tử <codeBase> tương ứng với assembly, bộ thực thi sẽ tìm assembly bằng kỹ thuật probing. Quá trình probing sẽ tìm file đầu tiên có tên của assembly (với phần mở rộng là .dll hay .exe) trong các vị trí: • Thư mục gốc của ứng dụng. • Các thư mục con của thư mục gốc phù hợp với tên và bản địa của assembly. • Các thư mục con (của thư mục gốc) do người dùng chỉ định. Phương thức Load là cách dễ nhất để tìm và nạp các assembly, nhưng cũng có thể tốn nhiều chi phí cho việc dò trong nhiều thư mục để tìm các assembly có tên yếu. Phương thức LoadFrom cho phép bạn nạp assembly từ một vị trí xác định, nếu không tìm thấy nó sẽ ném ngoại lệ FileNotFoundException. Bộ thực thi sẽ không cố tìm assembly như phương thức Load—phương thức LoadFrom không hỗ trợ GAC, policy , phần tử <codeBase> hay probing. Dưới đây là đoạn mã trình bày cách sử dụng LoadFrom để nạp c:\shared\MySharedAssembly.dll. Lưu ý rằng, khác với Load, LoadFrom yêu cầu bạn chỉ định phần mở rộng của file assembly. // Nạp assembly có tên là c:\shared\MySharedAssembly.dll Assembly a4 = Assembly.LoadFrom(@"c:\shared\MySharedAssembly.dll"); . 3.7, và 3 .12 ). Tạo và kiểm tra các đặc tính tùy biến (các mục 3 .13 và 3 .14 ). 1. 1 Tạo miền ứng dụng V V Bạn cần tạo một miền ứng dụng mới. # # Sử dụng. và siêu dữ liệu (metadata), bao gồm: Tạo và hủy các miền ứng dụng (mục 3 .1 và 3.9). Làm việc với các kiểu và các đối tượng khi sử dụng nhiều miền ứng