初面网初面网

反射

反射是什么

反射 = 在代码运行时,通过字符串名字,去查看和调用代码编译后留下的"说明书(元数据)"。

获取类型结构

普通代码只能调用,反射可以在运行时把类的内部结构打印出来。

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"); // 直接调用,性能无损耗

总结

反射就三层意思:

  1. 看说明书:typeof(类名) 获取类的所有结构。
  2. 按名字找零件:GetMethod("方法名")、GetProperty("属性名")、GetField("字段名")。
  3. 操作零件:Invoke() 调用方法,SetValue() 改字段,CreateInstance() 造对象。
  • 写业务代码(增删改查)绝对不要用反射,性能差且代码晦涩。
  • 写框架、工具库、插件加载器时,反射是唯一解。

更新于 2026/8/16