GT C Sharp cơ bản – Bài 21 : Chương 20: Đặc tính
Chương 20: Đặc tính
Tác giả: Sưu tầm
Khái quát
Trong hầu hết các ngôn ngữ lập trình, nhiều thông tin được biểu diễn thông qua khai báo, và các thông tin khác được biểu diễn thông qua mã. Ví dụ, trong khai báo thành phần lớp sau
public int Test;
trình biên dịch và thời gian chạy sẽ dành vùng trống cho một biến nguyên và thiết đặt sự bảo vệ của nó để nó được truy xuất ở mọi nơi. Đây là một ví dụ về việc khai báo thông tin, nó tốt bởi vì sự kinh tế của biểu thức và bởi vì trình biên dịch xử lý các chi tiết cho chúng ta.
Tiểu biểu, các kiểu của thông tin khai báo được định nghĩa sẵn bởi người thiết kế ngôn ngữ và không thể mở rộng bởi người sử dụng ngôn ngữ. Ví dụ, người sử dụng nào muốn liên kết một trường cơ sở dữ liệu nhất định với một trường của một lớp phải tạo ra một cách biểu diễn mối quan hệ đó trong ngôn ngữ, một cách lưu trữ quan hệ, và một cách truy cập thông tin tại thời gian chạy. Trong một ngôn ngữ tựa C++, một macro phải được định nghĩa lưu giữ thông tin trong một trường mà nó là một phần của đối tượng. Những sơ đồ làm việc như vậy, nhưng chúng là những lỗi dễ gặp và không phổ biến. Chúng cũng đáng sợ.
.NET Runtime hỗ trợ các đặc tính, nó đơn thuần là những chú giải được đặt trong những phần tử của mã nguồn, như các lớp, thành phần, tham số, vân vân. Các đặc tính có thể được sử dụng để thay đổi hành vi của thời gian chạy, cung cấp thông tin giải quyết về một đối tượng, hay chuyển thông tin về tổ chức cho người thiết kế. Thông tin đặc tính được lưu trữ với dữ liệu mêta của phần tử và có thể được truy lục dễ dàng tại thời gian chạy thông qua một tiến trình là sự phản chiếu.
C# sử dụng đặc tính conditional để điều khiển khi nào các hàm thành phần được gọi. Cách sử dụng đặc tính conditional như sau:
using System.Diagnostics;
class Test {
[Conditional(“DEBUG”)]
public void Validate() { }
}
Hầu hết các lập trình viên sẽ sử dụng các đặc tính định nghĩa sẵn thường xuyên hơn là viết một lớp đặc tính.
Sử dụng đặc tính
Giả sử có một dự án mà một nhóm đang thực hiện, là quan trọng để theo dõi code review mà đã được thực hiện trong các lớp để nó có thể được xác định khi nào code review được kết thúc. Thông tin code review có thể được lưu trữ trong cơ sở dữ liệu, mà nó cho phép truy vấn dễ dàng tình trạng, hay nó có thể được lưu trữ trong các chú thích, mà làm cho dễ dàng trong tìm kiếm mã và thông tin cùng lúc.
Hoặc một đặc tính có thể được sử dụng, mà nó cho phép cả hai loại truy xuất trên. Để làm điều đó, một lớp đặc tính là cần thiết. Một lớp đặc tính định nghĩa tên của một đặc tính, cách nó có thể được tạo, và thông tin được lưu trữ. Các chi tiết phức tạp của việc khai báo các lớp đặc tính sẽ được nói rõ trong mục “Một đặc tính của riêng bạn”.
Lớp đặc tính sẽ như sau:
using System;
[AttributeUsage(AttributeTargets.Class)]
public class CodeReviewAttribute: System.Attribute {
public CodeReviewAttribute(string reviewer, string date) {
this.reviewer = reviewer;
this.date = date
}
public string Comment {
get {
return(comment);
}
set {
comment = value;
}
}
public string Date {
get {
return(date);
}
}
public string Reviewer {
get {
return(reviewer);
}
}
string reviewer
string date;
string comment;
}
[CodeReview(“Eric”, “01-12-2000″, Comment=”Bitchin’ Code”)]
class Complex {
}
Đặc tính AttributeUsage trước lớp chỉ định rằng đặc tính này có thể chỉ được đặc trong các lớp. Khi một đặc tính được sử dụng trên một thành phần chương trình, trình biên dịch kiểm tra xem có phải việc sử dụng đặc tính này trên thành phần chương trình đó là được cho phép.
Quy ước đặt tên cho các đặc tính là nối Attribute bào cuối tên lớp. Điều này làm cho dễ dàng hơn để báo những lớp nào là lớp đặc tính và những lớp nào là lớp bình thường. Tất cả các đặc tính phải dẫn xuất từ System.Attribute.
Lớp này định nghĩa một cấu tử đơn có một reviewer và date là các tham số, và nó cũng có một chuỗi public Comment.
Khi trình biên dịch gặp việc sử dụng đặc tính trên lớp Complex, đầu tiên nó tìm một lớp dẫn xuất từ Attribute có tên CodeReview. Nó không tìm thấy, nên nó tiếp theo tìm một lớp có tên CodeReviewAttribute, mà nó tìm thấy.
Tiếp theo, nó kiểm tra xem đó có phải là đặc tính cho phép trên một lớp.
Sau đó, nó kiểm tra xem có một cấu tử thoả các tham số chúng ta đã chỉ rõ trong cách sử dụng đặc tính hay không. Nếu nó tìm thấy, một thể hiện của đối tượng được tạo – cấu tử được gọi với các giá trị xác định.
Nếu có các tham số được đặt tên, thì nó đối sánh tên của tham số với một trường hoặc thuộc tính trong lớp đặc tính, và sau đó nó thiết đặt một trường hoặc thuộc tính một giá trị được chỉ định.
Sau khi điều này được thực hiện, tình trạng hiện hành của lớp đặc tính được lưu trong dữ liệu mêta cho thành phần chương trình cho những cái được chỉ định.
Ít nhất, đó là những cái xảy ra theo logic. Thực tế, nó chỉ gần giống như thế; xem mục “Gói đặc tính” để xem một mô tả cách nó được cài đặt.
Thêm một số chi tiết
Vài đặc tính chỉ có thể được sử dụng một lần trên một thành phần có sẵn. Những cái khác, được biết như các đặc tính đa sử dụng, có thể được sử dụng nhiều hơn là một lần. Ví dụ, điều này có thể được sử dụng để áp dụng vài đặc tính bảo mật cho một lớp đơn. Tư liệu trên một đặc tính sẽ mô tả có phải một đặc tính là đơn sử dụng hay đa sử dụng.
Trong hầu hết các trường hợp, rõ ràng đặc tính áp dụng cho một thành phần chương trình nhất định. Tuy nhiên, hãy xem xét trường hợp sau:
class Test {
[MarshalAs(UnmanagedType.LPWSTR)]
string GetMessage();
}
Trong hầu hết các trường hợp, đặc tính trong tình huống này sẽ áp dụng cho một hàm thành phần, nhưng đặc tính này thật ra liên qua đến kiểu trả về. Cách nào để trình biên dịch có thể thông báo sự khác biệt?
Có vài tình huống mà điều này có thể xảy ra:
- phương thức với giá trị trả về
- sự kiện với trường hay thuộc tính
- thẻ uỷ quyền với giá trị trả về
- thuộc tính với accessor với giá trị trả về của phương pháp nhận với tham số giá trị của phương pháp thiệt đặt
Đối với mỗi tình huống này, có một trường hợp phổ biến hơn các trường hợp khác, và nó trở thành trường hợp mặc định. Để xác định một thuộc tính cho một trường hợp phi mặc định, thành phần mà đặc tính áp dụng phải được chỉ định:
class Test {
[return:ReturnsHResult]
public void Execute() {}
}
return: báo rằng đặc tính này nên được áp dụng cho một giá trị trả về.
Thành phần có thể được xác định dù là không có sự nhập nhằng. Các định danh là như sau:
ĐỊNH DANH |
MÔ TẢ |
assembly |
Đặc tính trên một hợp tập |
modul |
Đặc tính trên một modul |
type |
Đặc tính trên một lớp hay cấu trúc |
method |
Đặc tính trên một phương thức |
property |
Đặc tính trên một thuộc tính |
event |
Đặc tính trên một sự kiện |
field |
Đặc tính trên một trường |
param |
Đặc tính trên một tham số |
returnValue |
Đặc tính trên giá trị trả về |
Các đặc tính được áp dụng cho các hợp tập và modul phải xuất hiện sau bất kỳ mệnh đề using và trước mã.
using System;
[assembly:CLSCompliant(true)]
class Test {
Test() {}
}
Ví dụ này áp dụng đặc tính ClsComplaint cho toàn bộ hợp tập. Tất cả các đặc tính cấp độ hợp tập khai báo trong bất kỳ tập tin nào mà nó là trong một hợp tập được nhóm lại với nhau và gắn vào một hợp tập.
Để sử dụng một đặc tính định nghĩa sẵn, hãy bắt đầu bằng cách tìm cấu tử đối sánh tốt nhất thông tin để được chuyển. Tiếp theo, viết đặc tính, truyền các tham số cho cấu tử. Cuối cùng, sử dụng cú pháp tham số được đặt tên để truyền thông tin thêm vào mà nó không phải là một phần của các tham số của cấu tử. Để có nhiều ví dụ về cách sử dụng đặc tính, hãy xem Chương 29, “Interop”.
Gói đặc tính
Có một vài lý do tại sao nó không thật sự làm việc theo cách nó được mô tả, và chúng liên quan đến hiệu suất. Đối với trình biên dịch để thật sự tạo một đối tượng đặc tính, môi trường .NET Runtime sẽ phải được thực thi, nên mỗi sự biên dịch sẽ phải khởi động môi trường, và mỗi trình biên dịch sẽ phải thực thi như một sự thực thi có quản.
Đồng thời, việc tạo đối tượng không thật sự cần thiết, vì chúng ta chỉ mới lưu trữ thông tin. Do đó, trình biên dịch xác nhận nó có thể tạo đối tượng đó, gọi cấu tử, và thiết đặt các giá trị cho bất kỳ tham số được đặt tên nào. Sau đó các tham số đặc tính được gói lại trong một đoạn thông tin nhị phân, mà nó được lưu giữ cùng với dữ liệu mêta của đối tượng.
Một đặc tính của riêng bạn
Để định nghĩa các lớp đặc tính và phản chiếu trên chúng tại thời gian chạy, có một ít vấn đề nữa cần xem xét. Mục này sẽ bàn luận về một số việc để xem xét khi nào thiết kế một đặc tính.
Có hai điều chính để xác định khi nào viết một đặc tính. Thứ nhất là các thành phần chương trình mà đặc tính được áp dụng, và thứ hai là thông tin sẽ được lưu trữ bởi đặc tính.
AttributeUsage
Đặt đặc tính AttributeUsage trên một lớp đặc tính điều khiển nơi nào đặc tính có thể được sử dụng. Các giá trị có thể cho đặc tính này được liệt kê trong kiểu liệt kê AttributeUsage và như sau:
GIÁ TRỊ |
Ý NGHĨA |
Assembly |
Một hợp tập chương trình |
Module |
Tập tin chương trình hiện tại |
Class |
Một lớp |
Struct |
Một cấu trúc |
Enum |
Một liệt kê |
Constructor |
Một cấu tử |
Method |
Một phương thức (hàm thành phần) |
Property |
Một thuộc tính |
Field |
Một trường |
Event |
Một sự kiện |
Interface |
Một giao diện |
Parameter |
Một tham số phương thức |
Return |
Giá trị trả về của phương thức |
Delegate |
Một thẻ uỷ quyền |
All |
Bất cứ ở đâu |
ClassMembers |
Lớp, cấu trúc, enum, cấu tử, phương thức, thuộc tính, trường, sự kiện, thẻ uỷ quyền, giao diện |
Như một phần của đặc tính AttributeUsage, một trong những giá trị này có thể được chỉ định hay một danh sách có thể được OR với nhau.
Đặc tính AttributeUsage cũng được sử dụng để chỉ định một đặc tính là đơn sử dụng hay đa sử dụng. Điều này được thực hiện với tham số được đặt tên AllowMultiple. Một thuộc tính như vậy giống như sau:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Event,
AllowMultiple = true)]
Các tham số đặc tính
Thông tin mà đặc tính sẽ lưu trữ nên được chia ra thành hai nhóm: thông tin được đòi hỏi cho mỗi lần sử dụng, và các mục tuỳ chọn.
Thông tin được đòi hỏi cho mỗi lần sử dụng nên được nhận thông qua cấu tử cho lớp đặc tính. Điều này buộc người sử dụng chỉ định tất cả các tham số khi chúng sử dụng đặc tính.
Các mục tuỳ chọn nên được cài đặt như các tham số được đặt tên, mà nó cho phép người sử dụng chỉ định bất cứ mục tuỳ chọn nào thích hợp.
Nếu một đặc tính có vài cách khác nhau mà nó có thể được tạo, với thông tin được đòi hỏi khác nhau, các cấu tử riêng biệt có thể được khai báo cho mỗi cách sử dụng. Đừng sử dụng các cấu tử tách biệt như một tuỳ chọn cho các mục tuỳ chọn.
Các kiểu tham số đặc tình
Định dạng gói đặc tính chỉ hỗ trợ một tập con các kiểu .NET Runtime, và do đó, chỉ một số kiểu có thể được sử dụng với các tham số đặc tính. Các kiểu được cho phép:
- bool, byte, char, double, float, int, long, short, string
- object
- System.Type
- enum có khả năng truy xuất public (không lồng nhau bên trong một số thứ phi public)
- mảng một chiều của các kiểu trên
Phản chiếu trên các đặc tính
Một khi các đặc tính được định nghĩa trên một số mã, là hữu ích để cho phép tìm kiếm các giá trị đặc tính. Điều này được thực hiện thông qua sự phản chiếu.
Mã sau trình bày một lớp đặc tính, một ứng dụng của đặc tính cho một lớp, và phản chiếu trên lớp đó để thu được đặc tính.
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Class)]
public class CodeReviewAttribute: System.Attribute {
public CodeReviewAttribute(string reviewer, string date) {
this.reviewer = reviewer;
this.date = date
}
public string Comment {
get {
return(comment);
}
set {
comment = value;
}
}
public string Date {
get {
return(date);
}
}
public string Reviewer {
get {
return(reviewer);
}
}
string reviewer;
string date;
string comment;
}
[CodeReview(“Eric”, “01-12-2000″, Comment=”Bitchin’ Code”)]
class Complex {
}
class Test {
public static void
System.Reflection.MemberInfo info
info = typeof(Complex);
object[] atts;
atts = info.GetCustomAttributes(typeof(CodeReviewAttribute))
if (atts.GetLength(0) != 0) {
CodeReviewAttribute att = (CodeReviewAttribute) atts[0]
Console.WriteLine(“Reviewer: {0}”, att.Reviewer);
Console.WriteLine(“Date: {0}”, att.Date);
Console.WriteLine(“Comment: {0}”, att.Comment);
}
}
}
Hàm Main() đầu tiên nhận một đối tượng kiểu kết hợp với kiểu Complex. Sau đó nó tải tất cả các đặc tính của kiểu CodeReviewAttribute. Nếu mảng các đặc tính có bất kỳ mục nhập nào, thành phần đầu tiên được chuyển đổi thành CodeReviewAttribute, và sau đó giá trị được in ra. Ở đây có thể chỉ có một mục trong mảng bởi vì CodeReviewAttribute là đơn sử dụng.
Ví dụ này cho ra kết quả sau:
Reviewer: Eric
Date: 01-12-2000
Comment: Bitchin’ Code
GetCustomAttribute() cũng có thể được gọi mà không cần một kiểu, để nhận tất cả các đặc tính tuỳ chọn trên một đối tượng.
Chú ý “CustomAttribute” trong ví dụ trước tham chiếu đến các đặc tính được lưu trữ trong phần đặc tính chung của dữ liệu mêta cho một đối tượng. Một số đặc tính .NET Runtime không được lưu trữ như các đặc tính tuỳ chọn trên đối tượng, nhưng được chuyển đổi thành các bit dữ liệu mêta trên đối tượng. Phép phản chiếu .NET không hỗ trợ việc xem các đặc tính này thông qua phép phản chiếu. Hạn chế này có thể được giải quyết trong các phiên bản tương lai của thời gian chạy.