CodeGym /课程 /C# SELF /使用反射

使用反射

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

1. 介绍

在 C# 中,任何类(或结构体 struct)都是一组成员。包括:

  • 字段 (fields):存数据的变量。
  • 属性 (properties):“聪明”的封装,背后有 get/set
  • 方法 (methods):功能逻辑。
  • 构造函数 (constructors):创建实例的方式。
  • 事件 (events):一种聪明的发布-订阅机制。

想象你有一个魔法放大镜,可以查看任何类型的内部,了解它的结构,甚至修改它。这就是反射。

命名空间和反射的主要类型

开始前别忘了引用命名空间:

using System.Reflection;

关键类和接口:

类/接口 说明
Type
主要入口 —— 一切从它开始
FieldInfo
字段信息
PropertyInfo
属性信息
MethodInfo
方法信息
ConstructorInfo
构造函数信息
EventInfo
事件信息
MemberInfo
上述所有的通用“父类”

2. 通过反射获取类型成员

获取字段和属性

示例:列举字段和属性

假设我们有这样的类:

public class Person
{
    public string Name { get; set; }
    public int Age;
    private string Secret = "秘密词";
}

获取字段:

Type personType = typeof(Person);

FieldInfo[] fields = personType.GetFields(
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var field in fields)
{
    Console.WriteLine($"字段: {field.Name}, 类型: {field.FieldType.Name}, 访问: {field.Attributes}");
}

获取属性:

PropertyInfo[] properties = personType.GetProperties(
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var property in properties)
{
    Console.WriteLine($"属性: {property.Name}, 类型: {property.PropertyType.Name}");
}

说明

  • BindingFlags 是一组“标志”,告诉你要查找哪些成员(public/private、instance/static 等)。
  • 字段和属性不一样:字段是数据片段,属性是背后的 get/set 方法,用来封装字段。

获取方法

MethodInfo[] methods = personType.GetMethods(
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

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

你会惊讶会看到很多方法。里面会有属性的 getter/setter,还有来自 object 的一些辅助方法!

实用过滤:如果只想拿你自己的“显式”方法,可以按名字或按 attribute 过滤。

用哪些 BindingFlags 去找什么

查找 调用示例 说明
公共字段
GetFields(BindingFlags.Public | ...)
对外可见的所有成员
私有字段
GetFields(BindingFlags.NonPublic | ...)
隐藏的内部成员
只有实例成员
BindingFlags.Instance
仅非静态的
只有静态成员
BindingFlags.Static
仅静态的
全部
BindingFlags.Public | NonPublic | Instance | Static
完整列表

3. 动态创建类型实例

为啥这很酷?
可以在编译时不知道确切类型的情况下创建对象。

简单方法:用 Activator.CreateInstance

// 以某种方式获取 Type,例如:
Type myType = typeof(Person);
// 创建对象:
object obj = Activator.CreateInstance(myType);
// 现在 obj 是 Person 的实例,但类型是 object

如果类没有无参构造函数,可以传参数:

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

// ...
Type animalType = typeof(Animal);
object dog = Activator.CreateInstance(animalType, "沙里克");

重要!

Console.WriteLine(dog.GetType().Name); // Animal

更定制的方法:通过 ConstructorInfo

ConstructorInfo ctor = animalType.GetConstructor(new Type[] { typeof(string) });
object dog = ctor.Invoke(new object[] { "沙里克" });

在实际开发中通常用 Activator,但有时候需要精确参数或操作私有构造函数 —— 这时 ConstructorInfo 就派上用场了。

4. 通过名字访问字段和属性

获取并修改字段

Person p = new Person();
Type t = p.GetType();

FieldInfo field = t.GetField("Age");
field.SetValue(p, 42);
Console.WriteLine(field.GetValue(p)); // 42

操作私有字段:

FieldInfo secret = t.GetField("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
if (secret != null)
{
    secret.SetValue(p, "哎呀,秘密被揭露了");
    Console.WriteLine(secret.GetValue(p));
}
else
{
    Console.WriteLine("没找到字段 Secret");
}

注意!不要滥用对私有字段的访问——这是破坏封装,可能引入 bug。

获取并修改属性

PropertyInfo pi = t.GetProperty("Name");
pi.SetValue(p, "瓦西娅");
Console.WriteLine(pi.GetValue(p)); // 瓦西娅

属性不是字段,而是背后的 get/set 方法。

为什么有时不起作用?

如果你的字段或属性是私有的,记得加上合适的 BindingFlags(例如,NonPublic)。如果属性没有 setter,你就无法通过反射写入——这个规则依然生效。

5. 按名字调用方法

调用无参数的简单方法

public class Greeter
{
    public void SayHello()
    {
        Console.WriteLine("你好!");
    }
}

Greeter g = new Greeter();
Type t = g.GetType();
MethodInfo mi = t.GetMethod("SayHello");
mi.Invoke(g, null); // 你好!

调用带参数的方法

public class MathOp
{
    public int Add(int x, int y) => x + y;
}

MathOp calc = new MathOp();
Type t = calc.GetType();
MethodInfo mi = t.GetMethod("Add");
object result = mi.Invoke(calc, new object[] { 7, 5 });
Console.WriteLine(result); // 12

如果方法是静态的,在 Invoke 的第一个参数传入 null

处理重载方法

MethodInfo mi = t.GetMethod(
    "Add", 
    new Type[] { typeof(int), typeof(int) }
);

或者遍历并手动过滤:

foreach(var method in t.GetMethods()) {
    if (method.Name == "Add" && method.GetParameters().Length == 2) 
    {
        // ... 我们的方法!
    }
}

6. 有用的细节

用于分析类型的主要成员、方法和属性

类成员 获取信息的方法 返回类 如何读取/修改值
字段 (field)
GetField, GetFields
FieldInfo
.GetValue(obj), .SetValue(obj, x)
属性 (prop)
GetProperty, GetProperties
PropertyInfo
.GetValue(obj), .SetValue(obj, x)
方法 (method)
GetMethod, GetMethods
MethodInfo
.Invoke(obj, 参数)
构造函数
GetConstructor, GetConstructors
ConstructorInfo
.Invoke(参数)

这些在真实世界中有什么用?

  • 在 ORM 框架(例如,Entity Framework 根据你的模型构建 SQL 查询)。
  • 在序列化和解析器系统中(例如,JSON 序列化器)。
  • 用于通用库:日志、测试、UI 生成器。
  • 面试题常问:你会如何写一个通用的对象复制器?
  • 写插件时也常用,当你的程序加载并调用别人写的代码。

即使现在觉得这像黑客电影里的魔法,信我——会有用的!

7. 常见错误和注意点

开发者经常把 BindingFlags 搞混 —— 看不到私有字段,或者反而得到太多方法。始终检查标志的组合:对私有字段使用 NonPublic,对静态使用 Static,等等。

通过 Invoke 调用方法时,按声明的顺序和类型传入参数。错误的顺序/类型会导致 TargetParameterCountExceptionArgumentException。返回值会以 object 的形式返回——别忘了转换类型(或者用 pattern matching)。

别忘了性能:反射比直接调用慢。在热点代码里缓存找到的 Type/MethodInfo/PropertyInfo,或者提前生成 delegate。

你经常会看到像 get_PropertyNameset_PropertyName 这样的辅助方法。这是属性的访问方法——不需要自己去创建它们。

如果你修改私有字段或属性——你是在破坏封装。误用可能导致难排查的错误。正如一句话所说:“能力越大,责任越大” —— 小心使用,尤其是在库项目里。

2
任务
C# SELF, 第 63 级, 课程 1
已锁定
通过反射调用方法
通过反射调用方法
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION