CodeGym /课程 /C# SELF /反射: System.Reflection

反射: System.Reflection

C# SELF
第 63 级 , 课程 0
可用

1. 介绍

反射(来自英文 Reflection)是程序在运行时检查自身结构的能力。想象你的程序可以问自己:'我有哪些方法?这个类型有什么属性?'并在运行时得到答案。

现实中反射的应用示例

  • 创建插件和模块:你不需要事先知道要操作哪个类——只要加载 DLL,在运行时查看它的方法即可。
  • 测试自动化:测试框架,比如 xUnit 和 NUnit,都是通过反射找到测试方法的。
  • 序列化:用反射可以把任意对象自动转换成 JSON/XML,而不需要手动遍历属性。
  • 基于属性的数据验证:比如你给某个属性加了 [Required],可以自动检测所有这样的属性。

面试里为啥要懂这些

反射相关的能力在面试中常被问到,因为它展示了你对 .NET 内部机制的理解和处理动态问题的能力。如果你想写自己的框架或扩展,反射是必备技能。

2. 命名空间和反射的基本类

C# 中所有反射功能都在命名空间 System.Reflection 里。别忘了在文件开头:

using System.Reflection;

反射的主要类型

类/类型 说明
Type
表示 .NET 中的类型描述(例如 class、interface、enum 等)
PropertyInfo
类型的属性
MethodInfo
类型的方法
FieldInfo
类型的字段
ConstructorInfo
类型的构造函数
Assembly
表示一个程序集(EXE 或 DLL)

Type 类的常用方法

方法 说明
GetProperties()
返回所有 public 属性
GetFields()
返回所有 public 字段
GetMethods()
返回所有 public 方法
GetConstructors()
返回所有 public 构造函数
GetInterfaces()
返回类型实现的所有接口
GetCustomAttributes()
返回应用在类型上的所有属性(attributes)

通常可以给这些方法传入 BindingFlags 来访问 private 或 static 成员,但我们先只看基本场景。

3. 第一步:获取 Type 对象

所有反射的操作都以 Type 对象为基础。

有几种方式可以拿到这个对象,我们来看看每种:

方式 1:通过对象

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

方式 2:通过类型字面量

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

方式 3:通过字符串(动态)

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

注意!对于来自其他程序集的自定义类,需要带上程序集信息的完整类型名。

4. 探索类型结构:属性、方法、字段和构造函数

现在到了关键时刻:我们有了 Type 对象——可以查任何想知道的东西了。

获取属性列表

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

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

输出:

Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...

获取方法列表

MethodInfo[] methods = type.GetMethods();

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

大型类型的方法可能非常多!通常你不需要全部方法,而是只关心“自己的”方法(比如排除从 object 继承来的)。我们稍后会处理这个问题。

获取字段列表

FieldInfo[] fields = type.GetFields();

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

但大多数常规类没有 public 字段,因为封装很重要 :)

获取构造函数

ConstructorInfo[] constructors = type.GetConstructors();

foreach (var ctor in constructors)
{
    Console.WriteLine($"构造函数: {ctor}");
}

5. 在实践中用反射:分析我们的应用

提醒一下,我们的练习项目里有个类 TaskItem,用来存任务。我们来用反射看看它的结构。

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

下面是运行时查看这个类型内部信息的做法:

Type taskType = typeof(TaskItem);

Console.WriteLine("属性:");
foreach (var prop in taskType.GetProperties())
    Console.WriteLine($"  {prop.PropertyType.Name} {prop.Name}");

Console.WriteLine("字段:");
foreach (var field in taskType.GetFields())
    Console.WriteLine($"  {field.FieldType.Name} {field.Name}");

Console.WriteLine("方法:");
foreach (var method in taskType.GetMethods())
    Console.WriteLine($"  {method.ReturnType.Name} {method.Name}()");

你可以试着在类里添加新属性,观察反射如何发现它们。这就是通用序列化器和对象检查器的工作方式。

6. 可视化:Type 对象的结构(示意)


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

这就是我们的“类型画像”——它有名字、程序集、一组成员和 attributes。

获取类型的基本信息

Type 对象可以告诉你这是哪种类型:

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. 按名称查找具体的属性、方法或字段

属性

var prop = taskType.GetProperty("Title");
if (prop != null)
{
    Console.WriteLine($"属性 'Title' 的类型: {prop.PropertyType}");
}

方法

var method = taskType.GetMethod("ToString");
if (method != null)
{
    Console.WriteLine($"方法 'ToString' 返回: {method.ReturnType}");
}

字段

var field = taskType.GetField("IsCompleted");
if (field != null)
{
    Console.WriteLine($"字段 'IsCompleted' 的类型: {field.FieldType}");
}

8. 用反射动态访问值

现在不仅查看,还可以去“动”值!

读取属性值

TaskItem item = new TaskItem { Id = 1, Title = "测试反射", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");

if (titleProp != null)
{
    object value = titleProp.GetValue(item);
    Console.WriteLine($"标题: {value}");
}

设置属性值

titleProp.SetValue(item, "修改后的标题");
Console.WriteLine(item.Title);

操作字段

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

访问私有字段和属性需要特殊的 flags,比如 BindingFlags.NonPublic。稍后会详细讲。

9. 通过反射调用方法

即使在编写代码时不知道方法,也可以调用它们!

TaskItem task = new TaskItem { Title = "反射很酷!" };
MethodInfo method = task.GetType().GetMethod("ToString");

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

如果方法有参数,把它们作为对象数组传入:

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. 通过构造函数动态创建对象

反射允许创建对象,甚至不用 new 操作符!

Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// 这是同一个 TaskItem(但类型是 object)
Console.WriteLine(newTask.GetType().Name); // TaskItem

也可以传构造函数参数:

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

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

11. 使用 System.Reflection 时的常见错误

错误 №1:在没有 BindingFlags 的情况下访问私有成员。
默认情况下,反射只能看到 public 成员。没有 BindingFlags.NonPublic 就无法访问私有字段或方法。

错误 №2:忽略对 null 的检查。
方法 GetPropertyGetMethod 等可能返回 null(成员没找到)。不做检查会导致 NullReferenceException

错误 №3:选择错误的方法重载。
如果存在多个方法重载,GetMethod 可能返回不是你想要的那个。可以使用带参数类型的 GetMethod 来明确选择。

错误 №4:在性能敏感代码中滥用反射。
反射比直接调用慢。对性能有要求的代码要缓存元数据或使用委托(delegates)。

2
任务
C# SELF, 第 63 级, 课程 0
已锁定
获取类型的属性、方法和字段列表
获取类型的属性、方法和字段列表
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION