C#反射的概念与实战

一、什么是反射?

  1. 一句话定义

    反射是一种"在运行时(Run-Time)而不是编译时(Compile-Time)去探查、创建、调用、修改程序集(Assembly)、类型(Type)、成员(Member)"的 API。

  2. 类比

    把程序当成一辆行驶中的高铁:

    • 正常情况下,我们只能在车站(编译期)知道每节车厢(类)里有哪些座位(字段/属性/方法)。

    • 有了反射,就相当于在高铁飞驰时还能临时打开任意一节车厢、查看座位、甚至把乘客换到别的车厢。

  3. 反射能做什么

    • 动态加载 dll(插件式架构)

    • 读取/设置私有成员(序列化、ORM、Mock)

    • 根据配置文件字符串创建对象(工厂模式)

    • 读取自定义 Attribute(AOP、验证框架)

    • 运行时生成代理(动态代理、远程调用)

二、核心 API 速查表

| 类/命名空间 | | 作用 | |----| | | 常用成员 | |------| | | 备注 | |----| |
|-----------------------------------|-------------------------|-------------------------------------------------------------------|---------------------------|
| System.Type | 描述"一个类型"的所有元数据 | GetMethods(), GetProperties(), GetFields(), GetCustomAttributes() | typeof(T) 或 obj.GetType() |
| System.Reflection.Assembly | |---------| | 描述一个程序集 | | Load(), LoadFrom(), GetTypes() | dll/exe 文件 |
| System.Reflection.PropertyInfo | |------| | 描述属性 | | GetValue(), SetValue() | 支持索引器 |
| System.Reflection.MethodInfo | |------| | 描述方法 | | Invoke() | 静态方法传 null 为实例 |
| System.Reflection.ConstructorInfo | 描述构造函数 | Invoke() | 创建实例 |
| System.Activator | 快捷创建对象 | CreateInstance(Type) | 内部还是调用 ConstructorInfo |

三、最小可运行案例

1:运行时探查对象

cs 复制代码
using System;
using System.Reflection;

class Person
{
    public string Name { get; set; }
    private int age;
    public Person(int age) { this.age = age; }
    private void SayHi() => Console.WriteLine($"Hi, {Name}, age {age}");
}

class Program
{
    static void Main()
    {
        Person p = new Person(18) { Name = "Tom" };
        Type t = p.GetType();                       // 1. 拿到 Type
        Console.WriteLine($"类名:{t.FullName}");

        // 2. 遍历属性
        foreach (var prop in t.GetProperties())
            Console.WriteLine($"属性:{prop.Name}={prop.GetValue(p)}");

        // 3. 读取私有字段
        FieldInfo ageField = t.GetField("age", BindingFlags.NonPublic | BindingFlags.Instance);
        Console.WriteLine($"私有字段 age={ageField.GetValue(p)}");

        // 4. 调用私有方法
        MethodInfo hi = t.GetMethod("SayHi", BindingFlags.NonPublic | BindingFlags.Instance);
        hi.Invoke(p, null);
    }
}

运行结果

cs 复制代码
类名:Person
属性:Name=Tom
私有字段 age=18
Hi, Tom, age 18

四、最小可运行案例

2:动态加载外部 DLL(插件)

场景:主程序在不重新编译的情况下,加载一个计算插件。

  1. 新建类库项目 CalculatorPlugin,编译生成 CalculatorPlugin.dll

    cs 复制代码
    public class Adder
    {
        public double Compute(double a, double b) => a + b;
        public string Name => "加法器";
    }
  2. 主程序(控制台 exe)

    cs 复制代码
    using System;
    using System.Reflection;
    
    class Program
    {
        static void Main()
        {
            // 1. 运行时把 dll 读进来
            Assembly asm = Assembly.LoadFrom(@"plugins\CalculatorPlugin.dll");
    
            // 2. 找到类型 Adder
            Type adderType = asm.GetType("CalculatorPlugin.Adder");
    
            // 3. 创建实例
            object adder = Activator.CreateInstance(adderType);
    
            // 4. 调用方法
            MethodInfo compute = adderType.GetMethod("Compute");
            double result = (double)compute.Invoke(adder, new object[] { 3, 4 });
    
            // 5. 读取属性
            string name = (string)adderType.GetProperty("Name").GetValue(adder);
    
            Console.WriteLine($"{name} 结果:{result}");
        }
    }

    把 dll 放到 exe 同级的 plugins 目录即可运行。

五、最小可运行案例

3:利用 Attribute + 反射做简易验证框架

cs 复制代码
using System;
using System.Reflection;

[AttributeUsage(AttributeTargets.Property)]
class RangeAttribute : Attribute
{
    public int Min { get; }
    public int Max { get; }
    public RangeAttribute(int min, int max) { Min = min; Max = max; }
}

class Student
{
    [Range(0, 150)]
    public int Score { get; set; }
}

static class Validator
{
    public static bool Validate(object obj)
    {
        foreach (var prop in obj.GetType().GetProperties())
        {
            var range = prop.GetCustomAttribute<RangeAttribute>();
            if (range != null)
            {
                int value = (int)prop.GetValue(obj);
                if (value < range.Min || value > range.Max)
                {
                    Console.WriteLine($"{prop.Name}={value} 不在范围 {range.Min}-{range.Max}");
                    return false;
                }
            }
        }
        return true;
    }
}

class Program
{
    static void Main()
    {
        var stu = new Student { Score = 200 };
        bool ok = Validator.Validate(stu);   // 输出提示
    }
}

六、性能注意点

1.反射调用比直接调用慢 2~3 个数量级,频繁调用需缓存:

cs 复制代码
// 只获取一次 MethodInfo 并缓存
private static readonly MethodInfo _method = typeof(Foo).GetMethod("Bar");
  1. .NET 7+ 可用 source generatorsIncremental Generators 在编译期生成代码,消除运行时反射。

  2. 使用 Delegate.CreateDelegate 把 MethodInfo 转成强类型委托,可接近原生性能。

七、常见错误及排查清单

|-------------------------------------------------|-------------------------------------|------------------------|
| 错误信息 | | 原因 | |----| | | 解决 | |----| |
| System.NullReferenceException | GetMethod/GetField 返回 null | 忘记 BindingFlags |
| System.Reflection.TargetException | |---------------| | 实例方法用 null 调用 | | |--------| | 传入正确实例 | |
| System.Reflection.TargetParameterCountException | 参数个数不匹配 | 检查 Invoke 的 object[] |
| System.IO.FileNotFoundException | LoadFrom 路径不对 | 使用绝对路径或 AppDomain 相对路径 |

"反射就是 .NET 给我们的'运行时显微镜 + 手术刀':先用 Type 看,再用 Invoke/SetValue 切,就能把原本编译期写死的逻辑变成可配置、可扩展的活代码。"

相关推荐
杨DaB14 分钟前
【SpringMVC】拦截器,实现小型登录验证
java·开发语言·后端·servlet·mvc
近津薪荼15 分钟前
c++详解(宏与内联函数,nullptr)
开发语言·c++
R-G-B2 小时前
【12】大恒相机SDK C#开发 ——多相机开发,枚举所有相机,并按配置文件中的相机顺序 将所有相机加入设备列表,以便于对每个指定的相机操作
c#·大恒相机sdk·大恒多相机开发·大恒多相机枚举·大恒多相机指定顺序
R-G-B2 小时前
【13】大恒相机SDK C#开发 —— Fom1中实时处理的8个图像 实时显示在Form2界面的 pictureBox中
c#·大恒相机sdk·图像实时显示在另一个界面
天若有情6736 小时前
【python】Python爬虫入门教程:使用requests库
开发语言·爬虫·python·网络爬虫·request
寒水馨6 小时前
Java 17 新特性解析与代码示例
java·开发语言·jdk17·新特性·java17
启山智软6 小时前
选用Java开发商城的优势
java·开发语言
秃然想通6 小时前
掌握Python三大语句:顺序、条件与循环
开发语言·python·numpy
##学无止境##7 小时前
Maven 从入门到精通:Java 项目构建与依赖管理全解析(上)
java·开发语言·maven