Reflection là gì?
Reflection trong C# là một tính năng mạnh mẽ, cho phép các lập trình viên kiểm tra và thay đổi các kiểu (types), thành viên (members), và đối tượng (objects) trong quá trình chạy chương trình (runtime).
Truy Xuất Thông Tin Kiểu Sử Dụng Reflection trong C#
Reflection trong C# cho phép chúng ta xem xét và điều chỉnh các kiểu trong thời gian chạy. Nó cung cấp khả năng truy xuất thông tin về các lớp (classes), giao diện (interfaces), phương thức (methods), thuộc tính (properties), và nhiều hơn nữa. Hiểu cách truy xuất kiểu bằng cách sử dụng reflection là quan trọng để xây dựng ứng dụng C# phức tạp và sáng tạo hơn.
Để truy xuất kiểu bằng cách sử dụng reflection, chúng ta có thể sử dụng lớp Type từ không gian tên System. Lớp Type cung cấp các phương thức và thuộc tính khác nhau để làm việc với các kiểu động thời gian chạy. Dưới đây là một đoạn mã mẫu minh họa cách truy xuất kiểu bằng reflection:
using System;
public class Program
{
public static void Main()
{
// Get the type of a class
Type carType = typeof(Car);
Console.WriteLine($"Type Name: {carType.Name}");
// Get all public methods of a class
MethodInfo[] methods = carType.GetMethods();
Console.WriteLine("Methods:");
foreach (MethodInfo method in methods)
{
Console.WriteLine($"- {method.Name}");
}
// Get all properties of a class
PropertyInfo[] properties = carType.GetProperties();
Console.WriteLine("Properties:");
foreach (PropertyInfo property in properties)
{
Console.WriteLine($"- {property.Name}");
}
}
}
public class Car
{
public string Make { get; set; }
public string Model { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public void StopEngine()
{
Console.WriteLine("Engine stopped");
}
}
Trong đoạn mã trên, chúng ta đầu tiên sử dụng toán tử typeof để lấy kiểu của lớp Car. Sau đó, chúng ta có thể truy cập các thông tin khác nhau về kiểu. Trong ví dụ này, chúng ta thu được tên (name) của kiểu, tất cả các phương thức công khai (public methods) và tất cả các thuộc tính. Khi chạy mã này, kết quả sẽ là như sau:
Type Name: Car
Methods:
- StartEngine
- StopEngine
Properties:
- Make
- Model
Truy xuất các kiểu bằng cách sử dụng reflection có thể hữu ích trong nhiều tình huống khác nhau. Ví dụ: bạn có thể muốn tải động và khởi tạo các lớp dựa trên dữ liệu cấu hình hoặc đầu vào của người dùng. Reflection cũng cho phép bạn kiểm tra cấu trúc và hành vi của mã trong thời gian chạy và điều này mở ra một số cánh cửa khi bạn cần đưa ra quyết định dựa trên thông tin mà bạn không có tại thời điểm biên dịch.
Truy cập các thành viên không công khai bằng cách sử dụng Reflection trong C#
Ở phần trên, reflection cho phép chúng ta truy cập những thứ như phương thức và thuộc tính công khai có sẵn trên các kiểu. Trong phần này chúng ta còn biết thêm khả năng của reflection cho phép tra cứu thông tin không công khai (non-public) về các kiểu. Sau đây là đoạn mã minh họa:
using System;
using System.Reflection;
public class Person
{
private string _name;
private int Age { get; set; }
private void SayHello()
{
Console.WriteLine("Hello!");
}
}
public class Program
{
public static void Main()
{
Type personType = typeof(Person);
// Accessing a field using reflection
FieldInfo nameField = personType.GetField(
"_name",
BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine($"Field Name: {nameField.Name}");
// Accessing a private property using reflection
PropertyInfo ageProperty = personType.GetProperty(
"Age"
BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine($"Property Name: {ageProperty.Name}");
// Accessing a private method using reflection
MethodInfo sayHelloMethod = personType.GetMethod(
"SayHello",
BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine($"Method Name: {sayHelloMethod.Name}");
}
}
Trong đoạn mã trên, chúng ta định nghĩa một lớp Person với một trường riêng tư (private) tên là _name, một thuộc tính riêng tư Age, và một phương thức riêng tư SayHello. Trong phương thức Main, chúng ta thu được đối tượng Type cho lớp Person bằng cách sử dụng typeof(Person). Bằng cách sử dụng reflection, chúng ta có thể truy cập trường, thuộc tính và phương thức bằng cách chỉ định tên của chúng và sử dụng các phương thức GetField, GetProperty và GetMethod của lớp Type, tương ứng, để thu được các thành viên tương ứng.
Nhưng chú ý phần cực kỳ quan trọng ở đây? Đó là BindingFlags enum. Chúng ta có thể yêu cầu các thành viên không công khai (non-public) của thể hiện. Chúng ta kết hợp hai loại thành viên này với nhau bằng cách sử dụng các enum cờ (flag enums). Một số nguồn tham khảo:
- Xem thêm về BindingFlags Enum tại đây.
- Video về các enum cờ (flag enum)
Sử Dụng Reflection để Sửa Đổi Đối Tượng
Reflection trong C# không chỉ cho phép chúng ta xem xét và lấy thông tin về đối tượng, mà còn mang lại khả năng sửa đổi dữ liệu và hành vi của chúng trong thời gian chương trình chạy. Điều này có nghĩa là chúng ta có thể, trong thời gian chạy, lấy một thành viên theo tên (và điều kiện khác) và sau đó thực hiện các sửa đổi — hiệu quả là bỏ qua nhiều kiểm tra thời gian biên dịch mà chúng ta thường xuyên nhận được. Để minh họa điều này, hãy xem xét một tình huống trong đó chúng ta có một lớp Person đơn giản với các thuộc tính đại diện cho Name và Age:
public class Person
{
public string Name { get; set; }
// NOTE: this probably makes no sense to ever
// just have a private property like this here
// but it's just for demonstration
private int Age { get; set; }
public void PrintInfo()
{
Console.WriteLine($"{Name} - {Age} years old");
}
}
Bây giờ, hãy giả sử chúng ta muốn thay đổi Age của một đối tượng Person động (trong thời gian chương trình chạy) dựa trên một điều kiện nào đó . Bằng reflection, chúng ta có thể đạt được điều này bằng cách truy cập và sửa đổi thuộc tính Age của Person trong thời gian chạy:
// Create a new instance of the Person class
Person person = new Person()
{
Name = "Dev Leader",
};
// Get the Type object for the Person class
Type personType = typeof(Person);
// Get the PropertyInfo object for the Age property
PropertyInfo ageProperty = personType.GetProperty(
"Age",
BindingFlags.NonPublic | BindingFlags.Instance);
// Set the value of the Age property to 35
ageProperty.SetValue(person, 35);
// Prints "Dev Leader - 35 years old")
person.PrintInfo();
Trong ví dụ mã này, trước tiên chúng ta tạo một thể hiện của lớp Person. Sau đó, sử dụng reflection, chúng ta thu được đối tượng Type cho lớp Person. Từ đối tượng Type, chúng ta lấy đối tượng PropertyInfo cho thuộc tính Age, thuộc tính này là riêng tư và không thể truy cập theo cách thông thường từ bên ngoài. Cuối cùng, chúng ta sử dụng phương thức SetValue của đối tượng PropertyInfo để sửa đổi thuộc tính Age của đối tượng person thành 35. Khi chúng ta yêu cầu thể hiện in thông tin của mình, chúng ta thấy giá trị đã được cập nhật!
Mặc dù khả năng sửa đổi đối tượng động có thể vô cùng mạnh mẽ, nó cũng đi kèm với một số rủi ro và xem xét. Sửa đổi đối tượng bằng reflection có thể đưa vào hệ thống sự phức tạp và những rủi ro tiềm ẩn, chẳng hạn như vi phạm bảo vệ và việc vi phạm hành vi dự kiến của đối tượng. Quan trọng là phải thận trọng và đảm bảo rằng những sửa đổi được thực hiện bằng reflection phù hợp với thiết kế và yêu cầu của hệ thống — điều này có lẽ không nên là lựa chọn hàng đầu đối với nhiều vấn đề.
Tạo Đối Tượng Bằng Reflection
Reflection trong C# cho phép chúng ta tạo các thể hiện của các kiểu động thời gian chạy. Điều này có nghĩa là chúng ta có thể tạo đối tượng của một lớp cụ thể mà không cần biết tên lớp khi biên dịch. Tính linh hoạt này có thể hữu ích đặc biệt trong các tình huống chúng ta cần khởi tạo động thời gian chạy dựa trên dữ liệu hoặc cấu hình thời gian chạy.
Để tạo đối tượng động bằng reflection, chúng ta cần thực hiện một số bước:
- Chúng ta cần lấy một đối tượng Type đại diện cho lớp mà chúng ta muốn tạo một thể hiện. Chúng ta có thể làm điều này bằng cách sử dụng phương thức Type.GetType() và truyền tên đầy đủ của lớp dưới dạng chuỗi.
- Chúng ta có thể sử dụng phương thức Activator.CreateInstance() để tạo một thể hiện của lớp. Phương thức này nhận đối tượng Type làm tham số và trả về một thể hiện mới của lớp. Sau đó, chúng ta có thể ép kiểu thể hiện này thành kiểu mong muốn và sử dụng nó theo cách cần.
Hãy xem một ví dụ:
string className = "MyNamespace.MyClass";
Type classType = Type.GetType(className);
object instance = Activator.CreateInstance(classType);
MyClass myObject = (MyClass)instance;
Trong đoạn mã trên, chúng ta bắt đầu bằng cách xác định tên đầy đủ của lớp mà chúng ta muốn tạo một thể hiện (MyNamespace.MyClass). Sau đó, chúng ta lấy đối tượng Type bằng cách gọi Type.GetType() và truyền tên lớp dưới dạng chuỗi. Tiếp theo, chúng ta sử dụng Activator.CreateInstance() để tạo một thể hiện mới của lớp và ép kiểu nó thành kiểu mong muốn (MyClass trong trường hợp này).
Việc tạo đối tượng động bằng reflection có thể hữu ích trong nhiều tình huống khác nhau, bao gồm:
- Hệ thống plugin nơi chúng ta muốn tải các plugin khác nhau vào thời gian chạy. Bằng cách sử dụng reflection, chúng ta có thể tạo các thể hiện của các lớp plugin dựa trên cấu hình hoặc đầu vào của người dùng trong thời gian chạy.
- Tạo đối tượng động khi làm việc với deserialization. Ví dụ, nếu chúng ta nhận dữ liệu JSON hoặc XML và cần ánh xạ nó vào các lớp khác nhau dựa trên nội dung dữ liệu, reflection trong C# có thể giúp chúng ta tạo đối tượng phù hợp động.
Nhớ xử lý các ngoại lệ và đảm bảo kiểm tra lỗi đúng khi làm việc với reflection và tạo đối tượng động! Dễ quên rằng reflection mở ra một số cánh cửa kỳ quặc mà chúng ta thực sự cần phải cẩn trọng.
Nguồn CodeProject.