Attribute và reflection
Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 44 trên 70.
Thuộc tính là các thẻ siêu dữ liệu bạn gắn vào những phần tử code như lớp, phương thức hoặc thuộc tính. Chúng không trực tiếp thay đổi cách code chạy, nhưng cung cấp thông tin có thể đọc được khi chạy chương trình. Reflection là cơ chế đọc siêu dữ liệu này.
Bạn áp dụng một thuộc tính bằng cách đặt nó trong dấu ngoặc vuông phía trên phần tử mục tiêu:
[Obsolete("Use NewMethod instead")]
public void OldMethod() { }
[Serializable]
public class Person
{
public string Name { get; set; }
}Bạn có thể tạo thuộc tính tùy chỉnh bằng cách kế thừa từ Attribute:
public class AuthorAttribute : Attribute
{
public string Name { get; }
public AuthorAttribute(string name) => Name = name;
}
[Author("John")]
public class Calculator { }Reflection giúp bạn kiểm tra kiểu và đọc thuộc tính trong lúc chạy bằng không gian tên System.Reflection:
Type type = typeof(Calculator);
var attr = type.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine(attr.Name); // JohnBạn cũng có thể tìm hiểu các phương thức, thuộc tính và gọi chúng một cách linh hoạt:
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
Console.WriteLine(method.Name);Thuộc tính và reflection giúp nhiều framework hoạt động mạnh mẽ: các thư viện tuần tự hóa dùng chúng để kiểm soát đầu ra JSON, framework kiểm thử dùng chúng để tìm các phương thức kiểm thử, còn các vùng chứa dependency injection dùng chúng để tự động kết nối dịch vụ.
Thử thách
DễHãy xây dựng một hệ thống tìm plugin dùng các thuộc tính tùy chỉnh và reflection để tự động tìm, mô tả những lớp được đánh dấu là plugin. Cách này tương tự như cách các framework thực tế tìm thành phần, phương thức kiểm thử hoặc điểm cuối API trong thời gian chạy.
Bạn sẽ sắp xếp code trong ba tệp:
PluginAttribute.cs: Tạo một thuộc tính tùy chỉnh tên làPluginAttributetrong namespacePluginSystem. Thuộc tính này cần:- Kế thừa từ
Attribute - Có hai thuộc tính chỉ đọc:
Name(string) vàVersion(string) - Nhận cả hai giá trị thông qua hàm khởi tạo
- Kế thừa từ
Plugins.cs: Tạo một số lớp plugin trong namespacePluginSystem. Một số lớp cần được trang trí bằngPluginAttribute, còn một lớp thì không:ImageProcessor- được đánh dấu bằng[Plugin("Image Processor", "1.0")]DataExporter- được đánh dấu bằng[Plugin("Data Exporter", "2.5")]HelperUtility- KHÔNG được đánh dấu bằng bất kỳ thuộc tính nào (đây chỉ là một lớp thông thường)
Program.cs: Trong tệp chính, dùng reflection để tìm tất cả các lớp cóPluginAttribute. Với mỗi plugin tìm được, hãy in thông tin của nó. Bạn cần:- Lấy tất cả các kiểu từ assembly hiện tại
- Kiểm tra từng kiểu xem có
PluginAttributehay không - Lấy và hiển thị các thuộc tính
NamevàVersioncủa thuộc tính đó
Dùng System.Reflection để kiểm tra các kiểu và lấy các thuộc tính tùy chỉnh. Phương thức GetCustomAttribute<T>() trả về null nếu không có thuộc tính, vì vậy bạn có thể dùng cách này để lọc ra những lớp thực sự là plugin.
In từng plugin tìm được theo định dạng sau, mỗi plugin trên một dòng:
Plugin: {Name} (v{Version})In các plugin theo thứ tự sau: ImageProcessor trước, rồi đến DataExporter. Lớp HelperUtility không nên xuất hiện trong đầu ra vì lớp này không có thuộc tính.
Đầu ra dự kiến:
Plugin: Image Processor (v1.0)
Plugin: Data Exporter (v2.5)Thử thách này minh họa cách các framework dùng thuộc tính làm dấu hiệu và dùng reflection để tìm chúng. Kỹ thuật tương tự cũng là nền tảng cho các bộ chứa dependency injection, thư viện tuần tự hóa và framework kiểm thử!
Tự làm thử
using System;
using System.Reflection;
using PluginSystem;
class Program
{
public static void Main(string[] args)
{
// TODO: Lấy tất cả các kiểu từ assembly hiện tại
// Gợi ý: Dùng Assembly.GetExecutingAssembly().GetTypes()
// TODO: Duyệt qua từng kiểu và kiểm tra xem kiểu đó có thuộc tính (attribute) PluginAttribute không
// Gợi ý: Dùng type.GetCustomAttribute<PluginAttribute>()
// TODO: Nếu thuộc tính tồn tại, in ra thông tin plugin theo định dạng:
// Plugin: {Name} (v{Version})
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online