CodeGym /Các khóa học /C# SELF /Phản chiếu: System.Reflect...

Phản chiếu: System.Reflection

C# SELF
Mức độ, Bài học
Có sẵn

1. Giới thiệu

Reflection (từ tiếng Anh Reflection) — là khả năng của chương trình tự khám phá cấu trúc của chính nó trong khi chạy. Hãy tưởng tượng chương trình có thể tự hỏi: 'Mình có những phương thức nào? Kiểu này có những thuộc tính nào?' và nhận được câu trả lời tại runtime.

Ví dụ dùng reflection trong thực tế

  • Tạo plugin và module: Không cần biết trước bạn sẽ làm việc với lớp nào — chỉ cần load DLL và khám phá phương thức của nó khi chạy.
  • Tự động hóa testing: Các framework kiểm thử như xUnit và NUnit tìm các phương thức test bằng reflection.
  • Serialization: Dùng reflection có thể tự động biến bất kỳ đối tượng nào thành JSON/XML mà không phải lặp thủ công qua các thuộc tính.
  • Validate dữ liệu dựa trên attribute: Ví dụ bạn gắn attribute [Required] lên một thuộc tính — và muốn tự động kiểm tra tất cả các thuộc tính như vậy.

Tại sao nên biết điều này khi phỏng vấn

Kỹ năng dùng reflection thường được hỏi trong phỏng vấn vì nó cho thấy bạn hiểu cách .NET hoạt động và biết giải quyết các bài toán động. Nếu bạn muốn viết framework hoặc extension riêng, reflection là không thể thiếu.

2. Không gian tên và các lớp cơ bản của reflection

Toàn bộ chức năng reflection trong C# có sẵn qua không gian tên System.Reflection. Ở đầu file đừng quên:

using System.Reflection;

Các loại chính trong reflection

Lớp/Loại Mô tả
Type
Biểu diễn mô tả của một Type trong .NET (ví dụ class, interface, enum, v.v.)
PropertyInfo
Thông tin về một thuộc tính của kiểu
MethodInfo
Thông tin về một phương thức của kiểu
FieldInfo
Thông tin về một trường (field) của kiểu
ConstructorInfo
Thông tin về constructor của kiểu
Assembly
Biểu diễn một assembly (EXE hoặc DLL)

Các phương thức chính của lớp Type

Phương thức Mô tả
GetProperties()
Trả về tất cả các thuộc tính public
GetFields()
Trả về tất cả các field public
GetMethods()
Trả về tất cả các phương thức public
GetConstructors()
Trả về tất cả các constructor public
GetInterfaces()
Trả về tất cả interface mà kiểu này triển khai
GetCustomAttributes()
Trả về tất cả attribute áp dụng lên kiểu

Thường có thể truyền BindingFlags cho các phương thức này để truy cập thành viên private hoặc static, nhưng hiện tại ta giới hạn ở kịch bản cơ bản.

3. Bước đầu tiên: lấy đối tượng Type

Mọi kỹ thuật reflection đều dựa trên việc làm việc với đối tượng Type.

Có vài cách để lấy đối tượng này, cùng xem từng cách:

Cách 1: Qua một đối tượng

string text = "Hello, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String

Cách 2: Qua tên kiểu cố định

Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32

Cách 3: Theo chuỗi (động)

Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double

Chú ý! Với lớp người dùng từ assembly khác bạn phải chỉ rõ tên đầy đủ của type kèm assembly.

4. Khám phá cấu trúc kiểu: thuộc tính, phương thức, trường và constructor

Giờ là lúc kiểm chứng: ta đã có đối tượng Type — và bây giờ có thể biết mọi thứ mình muốn.

Lấy danh sách thuộc tính

Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();

foreach (var prop in properties)
{
    Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}

Kết quả:

Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...

Lấy danh sách phương thức

MethodInfo[] methods = type.GetMethods();

foreach (var method in methods)
{
    Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}

Các type lớn có thể có rất nhiều phương thức! Thường bạn không cần tất cả mà chỉ cần "của mình" (ví dụ loại trừ các phương thức kế thừa từ object). Chuyện này mình sẽ xử lý sau.

Lấy danh sách trường (fields)

FieldInfo[] fields = type.GetFields();

foreach (var field in fields)
{
    Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}

Nhưng hầu hết các lớp bình thường không có field public, vì encapsulation là quan trọng :)

Lấy constructor

ConstructorInfo[] constructors = type.GetConstructors();

foreach (var ctor in constructors)
{
    Console.WriteLine($"Hàm khởi tạo: {ctor}");
}

5. Áp dụng reflection thực tế: phân tích ứng dụng của chúng ta

Nhắc lại trong project học của chúng ta có lớp TaskItem, nơi lưu nhiệm vụ. Ta sẽ khám phá nó bằng reflection.

public class TaskItem
{
    public int Id { get; set; }
    public string Title { get; set; }
    public DateTime DueDate { get; set; }
    public bool IsCompleted;
}

Đây là cách biết toàn bộ "ruột" của kiểu trong runtime:

Type taskType = typeof(TaskItem);

Console.WriteLine("Thuộc tính:");
foreach (var prop in taskType.GetProperties())
    Console.WriteLine($"  {prop.PropertyType.Name} {prop.Name}");

Console.WriteLine("Trường:");
foreach (var field in taskType.GetFields())
    Console.WriteLine($"  {field.FieldType.Name} {field.Name}");

Console.WriteLine("Phương thức:");
foreach (var method in taskType.GetMethods())
    Console.WriteLine($"  {method.ReturnType.Name} {method.Name}()");

Thử thêm thuộc tính mới vào lớp và xem reflection phát hiện chúng như thế nào. Đó là cách serializer và object inspector generic hoạt động.

6. Minh họa: object Type trông như thế nào (sơ đồ)


+-------------------------+
|         Type            |
+-------------------------+
|  .Name                  |
|  .FullName              |
|  .Namespace             |
|  .Assembly              |
+-------------------------+
|  .GetProperties()       |
|  .GetFields()           |
|  .GetMethods()          |
|  .GetConstructors()     |
|  .GetInterfaces()       |
|  .GetCustomAttributes() |
+-------------------------+

Đây là "chân dung" của một type — có tên, assembly, tập các thành viên và attributes.

Lấy thông tin cơ bản về một kiểu

Đối tượng Type có thể cho biết đó là kiểu gì:

Type t = typeof(double);

Console.WriteLine(t.Name);         // Double
Console.WriteLine(t.FullName);     // System.Double
Console.WriteLine(t.Namespace);    // System
Console.WriteLine(t.IsClass);      // False
Console.WriteLine(t.IsValueType);  // True
Console.WriteLine(t.IsEnum);       // False
Console.WriteLine(t.IsPrimitive);  // True

7. Tìm cụ thể thuộc tính, phương thức hoặc trường theo tên

Thuộc tính

var prop = taskType.GetProperty("Title");
if (prop != null)
{
    Console.WriteLine($"Kiểu của thuộc tính 'Title': {prop.PropertyType}");
}

Phương thức

var method = taskType.GetMethod("ToString");
if (method != null)
{
    Console.WriteLine($"Phương thức 'ToString' trả về: {method.ReturnType}");
}

Trường

var field = taskType.GetField("IsCompleted");
if (field != null)
{
    Console.WriteLine($"Kiểu của trường 'IsCompleted': {field.FieldType}");
}

8. Dùng reflection để truy cập giá trị động

Giờ không chỉ xem mà còn có thể chạm tới giá trị!

Đọc giá trị thuộc tính

TaskItem item = new TaskItem { Id = 1, Title = "Kiểm thử Reflection", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");

if (titleProp != null)
{
    object value = titleProp.GetValue(item);
    Console.WriteLine($"Tiêu đề: {value}");
}

Gán giá trị cho thuộc tính

titleProp.SetValue(item, "Tiêu đề đã thay đổi");
Console.WriteLine(item.Title);

Làm việc với fields

FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true

Với field và thuộc tính private cần các flag đặc biệt, ví dụ BindingFlags.NonPublic. Mình sẽ nói về chuyện đó sau.

9. Gọi phương thức qua reflection

Bạn có thể gọi phương thức ngay cả khi không biết nó khi viết code!

TaskItem task = new TaskItem { Title = "Reflection — tuyệt!" };
MethodInfo method = task.GetType().GetMethod("ToString");

if (method != null)
{
    object result = method.Invoke(task, null);
    Console.WriteLine(result);
}

Nếu phương thức có tham số, truyền chúng như một mảng object:

public class Calculator
{
    public int Add(int a, int b) => a + b;
}

var calc = new Calculator();
var addMethod = typeof(Calculator).GetMethod("Add");
object[] parameters = { 5, 3 };
object sumResult = addMethod.Invoke(calc, parameters);
Console.WriteLine(sumResult); // 8

10. Tạo đối tượng động qua constructor

Reflection cho phép tạo đối tượng mà không cần toán tử new!

Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// Đây là cùng một TaskItem (nhưng kiểu là object)
Console.WriteLine(newTask.GetType().Name); // TaskItem

Có thể truyền tham số cho constructor:

public class User
{
    public string Name { get; }
    public User(string name) { Name = name; }
}

Type userType = typeof(User);
object user = Activator.CreateInstance(userType, "Alice");
Console.WriteLine(((User)user).Name); // Alice

11. Lỗi thường gặp khi làm việc với System.Reflection

Lỗi #1: Truy cập thành viên private mà không dùng BindingFlags.
Mặc định reflection chỉ thấy các thành viên public. Nếu không dùng BindingFlags.NonPublic thì không thể truy cập field hoặc phương thức private.

Lỗi #2: Bỏ qua kiểm tra trên null.
Các phương thức GetProperty, GetMethod và những phương thức khác có thể trả về null nếu không tìm thấy thành viên. Không kiểm tra sẽ dẫn tới NullReferenceException.

Lỗi #3: Chọn sai overload của phương thức.
Khi có nhiều overload, GetMethod có thể trả về overload không mong muốn. Dùng GetMethod với kiểu tham số để chỉ rõ hơn.

Lỗi #4: Dùng reflection trong code yêu cầu hiệu năng cao.
Reflection chậm hơn so với gọi trực tiếp. Với code cần hiệu năng, dùng cache metadata hoặc delegate.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Lấy danh sách các property, method và field của một type
Lấy danh sách các property, method và field của một type
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION