反射
反射是什么
反射 = 在代码运行时,通过字符串名字,去查看和调用代码编译后留下的"说明书(元数据)"。
获取类型结构
普通代码只能调用,反射可以在运行时把类的内部结构打印出来。
using System;
using System.Reflection;
public class User
{
public string Name { get; set; } // 属性
private int age; // 私有字段(普通代码访问不到)
public void Say(string msg) // 方法
{
Console.WriteLine($"{Name} 说: {msg}");
}
}
class Program
{
static void Main()
{
Type type = typeof(User); // 拿到 User 类的"说明书"
// 反射获取所有公开属性
PropertyInfo[] props = type.GetProperties();
foreach (var p in props)
Console.WriteLine($"公开属性名: {p.Name},类型: {p.PropertyType}");
// 反射获取所有方法(包括私有)
MethodInfo[] methods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
foreach (var m in methods)
Console.WriteLine($"方法名: {m.Name}");
}
}
输出:
公开属性名: Name,类型: System.String
方法名: Say
方法名: get_Name
方法名: set_Name
...
注意!反射连编译器自动生成的访问器(get_Name、set_Name)都能看到,也能获取私有字段。
动态创建对象(不用 new)
正常情况下必须 new User(),反射可以用类名字符串创建。
using System;
public class User
{
public User() { Console.WriteLine("无参构造函数执行"); }
public User(string name) { Console.WriteLine($"有参构造函数执行: {name}"); }
}
class Program
{
static void Main()
{
// 1. 用字符串拿到类型(简单场景要写全命名空间)
Type type = Type.GetType("User");
// 2. 调用无参构造函数创建对象
object obj1 = Activator.CreateInstance(type);
// 输出: 无参构造函数执行
// 3. 调用有参构造函数创建对象
object obj2 = Activator.CreateInstance(type, new object[] { "张三" });
// 输出: 有参构造函数执行: 张三
}
}
注意!创建出的对象类型是 object,编译时完全不知道它是 User。
通过字符串调用方法(不用点号)
最直观的差别:用字符串 "Say" 代替了代码里的 user.Say()。
using System;
using System.Reflection;
public class User
{
public void Say(string msg)
{
Console.WriteLine("输出: " + msg);
}
}
class Program
{
static void Main()
{
User user = new User();
Type type = typeof(User);
// 根据方法名字符串找到方法
MethodInfo method = type.GetMethod("Say");
// 调用它!参数打包成 object[] 数组
method.Invoke(user, new object[] { "Hello 反射" });
// 输出: 输出: Hello 反射
}
}
注意!方法名写错不会编译报错,但运行时会抛 NullReferenceException。
读写私有字段(突破权限)
正常代码无法修改私有字段 age,但反射可以。
using System;
using System.Reflection;
public class User
{
private int age = 10; // 私有字段
}
class Program
{
static void Main()
{
User user = new User();
Type type = typeof(User);
// 找到私有字段(注意要加 BindingFlags)
FieldInfo field = type.GetField("age", BindingFlags.NonPublic | BindingFlags.Instance);
// 读取私有字段的值
int oldValue = (int)field.GetValue(user);
Console.WriteLine($"修改前: {oldValue}"); // 输出: 10
// 修改私有字段的值
field.SetValue(user, 99);
int newValue = (int)field.GetValue(user);
Console.WriteLine($"修改后: {newValue}"); // 输出: 99
}
}
注意!这是反射破坏封装性的体现:平时写
user.age编译都过不去,反射却可以在运行时强行读写。
使用场景
反射的本质是"用字符串驱动",两个核心应用场景:
| 场景 | 为什么不用普通代码? | 反射的作用 |
|---|---|---|
| 插件系统 | 程序不知道用户会放什么 .dll 进来,无法写死 new XXX() | 用 Assembly.Load("插件.dll") 加载,再用字符串类名创建对象 |
| ORM 框架(如 Entity Framework) | 数据库列名和类属性名靠名字对应,编译器不知道 | 反射读取属性名拼 SQL,再把数据库返回值塞进对象 |
| 序列化/反序列化(如 Newtonsoft.Json) | JSON 里的字符串和类属性靠名字对应 | 反射找到叫 Name 的属性,把 "张三" 赋值进去 |
| 单元测试框架(如 xUnit) | 框架不知道测试方法叫什么名字 | 反射扫描类,找标记 [Test] 特性的方法并调用 |
与 JS 函数赋值的区别
JS 里把变量赋值为一个函数,和 C# 反射在"实现动态调用"的效果上类似,但底层原理完全不同:
- JS 函数赋值:编译时就确定,把函数当作数据(一等公民)传递,调用几乎零开销。
- C# 反射:运行时通过字符串在元数据里翻找,编译期无法发现错误、性能慢几十到上百倍、绕过强类型检查。
如果只是想在 C# 里"像 JS 一样"把函数赋给变量,应该用委托(Delegate)或 Lambda,而不是反射:
Func<string, string> myFunc = SayHello; // 直接赋值方法地址,编译时确定,类型安全
myFunc("World"); // 直接调用,性能无损耗
总结
反射就三层意思:
- 看说明书:
typeof(类名)获取类的所有结构。 - 按名字找零件:
GetMethod("方法名")、GetProperty("属性名")、GetField("字段名")。 - 操作零件:
Invoke()调用方法,SetValue()改字段,CreateInstance()造对象。
- 写业务代码(增删改查)绝对不要用反射,性能差且代码晦涩。
- 写框架、工具库、插件加载器时,反射是唯一解。