Muốn hiểu rõ hơn về attribute trong .Net, bạn phải tự đặt câu hỏi “Họ đã làm điều đó như thế nào?”, bạn phải tự tạo ra cho mình một thứ tương tự và trải nghiệm việc sử dụng những đó để trả lời cho câu hỏi của mình.
Tạo lớp Attribute
Để tạo một attribute mới, bạn phải tạo một lớp kế thừa từ System.Attribute đồng thời sử dụng attribute AttributeUsage để khai báo các thông tin cần thiết.
Khi đặt tên cho attribute sắp tạo ra này, bạn nên tuân theo quy tắc của Microsoft là thêm hậu tố “Attribute” vào tên lớp. Bạn không cần phải lo lắng nếu như tên lớp quá dài, khi sử dụng IDE sẽ tự động bỏ phần “Attribute” phía sau đi, và bạn có thể sử dụng cả hai tên của attribute, ví dụ như Obsolete và ObsoleteAttribute là tương đương nhau.
Bây giờ ta thử tạo ra một attribute đơn giản để lưu tên tác giả và địa chỉ web, một ví dụ rất thực tế và cần thiết:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class DemoAttribute : System.Attribute { private string author; public string Url { get; set; } public DemoAttribute(string author) { this.author = author; } public override string ToString() { return String.Format("Author: {0}\nLocation: {1}", Author, Url); } } |
Positional Parameter và Named Parameter
Bây giờ hãy tạo một lớp mới để test attribute này. Cách sử dụng tương tự như khi với các attribute của .Net.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class DemoClass { public void Print(string message) { Console.WriteLine(message); } } |
Bạn có thể thấy khi gõ đến phần tham số thứ hai của attribute trên, chức năng IntelliSense hiển thị tooltip hướng dẫn như sau:
DemoAttribute.DemoAttribute(string author, Named Parameters…)
Rõ ràng lớp DemoAttribute ta chỉ định nghĩa duy nhất một constructor yêu cầu tham số author, nhưng khi sử dụng ở trên bạn có lại có thể gán thêm giá trị cho tất cả property miễn là chúng có thể truy xuất được để gán dữ liệu (ngoại trừ các static property).
Mặc dù thông tin này không quan trọng nhưng bạn cũng cần biết để phân biệt rõ ràng. Trong một attribute phân biệt 2 loại tham số, một loại là Positional Parameter và loại kia là Named Parameter. Bạn có thể nhận biết chúng qua các tham số sử dụng trong constructor của attribute, tham số nào được sử dụng thì chúng là Positional Parameter và chúng là bắt buộc mỗi khi sử dụng attribute, còn lại là Named Parameter và chúng là tùy chọn.
Vậy trong attribute trên thì Author là Positional Parameter và Url là Named Parameter.
Các attribute parameter chỉ cho phép bạn sử dụng các kiểu dữ liệu đơn giản sau (trích từ MSDN):
- Simple types (bool, byte, char, short, int, long, float, and double)
- string
- System.Type
- enums
- object (The argument to an attribute parameter of type object must be a constant value of one of the above types.)
- One-dimensional arrays of any of the above types
Để gán giá trị cho các Named Parameter, bạn chỉ cần gõ tên của nó theo sau là dấu “=” và cuối cùng là giá trị cần gán, như ví dụ ở trên là cách tôi gán địa chỉ cho tham số Url.
Sử dụng AttributeUsage Attribute
Như đã nói lúc đầu, sau khi tạo lớp bạn phải dùng attribute AttributeUsage để xác định các kiểu thành phần nào có thể sử dụng được attribute mà bạn tạo ra và một số thông tin liên quan khác. Mặc định nếu không dùng AttributeUsage này để chỉ rõ thì giá trị của nó sẽ là All, tức là attribute của bạn có thể được sử dụng bởi mọi loại thành phần như class, struct, method, enum,…
Khai báo của attribute này có dạng sau:
[AttributeUsage(
ValidOn,
AllowMultiple=allowmultiple,
Inherited=inherited
)]
Trong đó:
– ValidOn: xác định thành phần nào có thể sử dụng được attribute. Giá trị này có kiểu enum AttributeTargets và có thể kết hợp nhiều giá trị với nhau bằng toán tử bit hoặc “|”. Mặc định là All.
– AllowMultiple: Một giá trị bool, chỉ định rằng attribute có cho phép sử dụng nhiều lần trên một thành phần không. Mặc định là false, giá trị true hầu như không bao giờ được sử dụng vì bạn chỉ cần dùng attribute một lần duy nhất là đủ để khai báo các thông tin cần thiết cho thành phần mà nó được gắn vào.
– Inherited: Một kiểu bool có giá trị mặc định là true. Tham số này xác định rằng attribute có thể được thừa kế từ một lớp cha mà nó đã được sử dụng không. Để hiểu rõ hơn tôi sẽ cung cấp một ví dụ nhỏ ở cuối bài.
Bây giờ là lúc bạn áp dụng những kiến thức vừa được trình bày, ở đây tôi muốn attribute DemoAttribute chỉ có thể được sử dụng bởi các class, struct và method. Các tham số AllowMultiple và Inherited không cần thay đổi. Vậy tôi gắn AttributeUsage vào lớp DemoAttribute như sau:
1 2 3 4 5 6 7 8 9 |
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method)] class DemoAttribute : System.Attribute { // […] } |
Truy xuất các attribute trong quá trình Runtime
Đây cũng là một kĩ thuật reflection mà tôi đã trình bày trong một bài trước đây. Bạn có thể dùng một trong hai cách sau để lấy về các Custom Attribute từ một kiểu dữ liệu nào đó (trong ví dụ này là lớp Program):
Attribute[] attributes = Attribute.GetCustomAttributes(typeof(Program));
object[] attributes = typeof(Program).GetCustomAttributes(true);
Cách đầu tiên thường được sử dụng hơn vì nó trả về một mảng Attribute và ta có thể sử dụng những thành phần của attribute mà không cần phải ép kiểu. Ngoài ra, bạn cũng có thể lấy các Custom Attribute từ các method, event, property,… bằng cách gọi phương thức GetCustomAttributes() tương ứng trong các đối tượng thuộc kiểu MemberInfo, MethodInfo, EventInfo,…
Ví dụ sau sẽ in ra thông tin của DemoAttribute mà ta gắn vào lớp Program, thông qua phương thức ToString():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[Demo("YinYang", Url = "https://yinyangit.wordpress.com")] class Program { static void Main(string[] args) { Attribute[] attributes = Attribute.GetCustomAttributes(typeof(Program)); foreach (var attr in attributes) { Console.WriteLine(attr); } Console.Read(); } |
Bạn hãy chạy thử và xem chương trình sẽ in ra đúng theo những gì mà ta đã viết trong phương thức ToString() của DemoAttribute.
Ví dụ về tham số Inherited của AttributeUsage
Đầu tiên bạn tạo ra hai lớp DemoClass1 và DemoClass2 thừa kế từ DemoClass1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
[Demo("DemoClass1")] class DemoClass1 { public virtual void Print(string message) { Console.WriteLine(message); } } class DemoClass2:DemoClass1 { public override void Print(string message) { base.Print(message); } } |
Bạn có thể tự hỏi liệu DemoClass2 có sẵn DemoAttribute kế thừa từ DemoClass1 không. Điều này tùy thuộc vào giá trị của tham số Inherited mà bạn gán khi dùng AttributeUsage cho lớp DemoAttribute. Bây giờ để mặc định Inherited=true, bạn hãy chạy thử phương thức Main() của lớp Program sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Program { static void Main(string[] args) { Attribute[] attributes = Attribute.GetCustomAttributes(typeof(DemoClass2)); foreach (var attr in attributes) { Console.WriteLine(attr); } Console.Read(); } |
Kết quả xuất ra là:
Author: DemoClass1
Mặc dù bạn lấy các Custom Attribute từ DemoClass2 nhưng kết quả in ra lại cho thấy nó có giá trị của attribute mà bạn gán cho DemoClass1. Nguyên nhân là attribute này đã được kế thừa từ DemoClass1 sang DemoClass2.
Bây giờ bạn hãy sửa tham số Inherited=false và chạy lại chương trình, kết quả là màn hình Console không xuất hiện gì ngoài dấu nhắc lệnh.
Từ ví dụ trên bạn có thể suy ra được công dụng của tham số Inherited này của AttributeUsage. Việc để tham số này có giá trị mặc định sẽ không chính xác trong ví dụ về DemoAttribute này. Khi bạn tạo một lớp kế thừa những thông tin tác giả từ lớp cha, trong khi tác giả lại là một người khác, vậy việc cần làm khi sau khi viết một lớp kế thừa là gắn một DemoAttribute vào lớp con vừa được tạo ra nhằm cập nhật thông tin hợp lý.