当前位置: 首页 > news >正文

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:运行时探查对象 

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. 拿到 TypeConsole.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);}
}

 运行结果

类名:Person
属性:Name=Tom
私有字段 age=18
Hi, Tom, age 18

四、最小可运行案例

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

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

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

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

    using System;
    using System.Reflection;class Program
    {static void Main(){// 1. 运行时把 dll 读进来Assembly asm = Assembly.LoadFrom(@"plugins\CalculatorPlugin.dll");// 2. 找到类型 AdderType 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 + 反射做简易验证框架 

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 个数量级,频繁调用需缓存:

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

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

七、常见错误及排查清单 

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

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

http://www.lryc.cn/news/606250.html

相关文章:

  • 网络编程-IP
  • TCP窗口缩放配置在云服务器高延迟网络中的参数调整测试
  • Android端RTMP低延迟播放器在工业与智能场景下的架构与落地
  • 抓大鹅小游戏微信抖音流量主小程序开源
  • TGD第九篇:三维应用——视频边缘检测
  • 【AI论文】MUR:面向大型语言模型的动量不确定性引导推理
  • cuda编程笔记(11)--学习cuBLAS的简单使用
  • Coze Studio概览(四)--Prompt 管理功能详细分析
  • 分布式锁的基本原理和基于lua脚本的实现(Redisson)
  • 红黑树×协程×内存序:2025 C++后端核心三体问题攻防手册
  • 旅游城市数量最大化 01背包问题
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘plotly’问题
  • Mac安装Navicat教程Navicat Premium for Mac v17.1.9 Mac安装navicat【亲测】
  • IK 字段级别词典的升级之路
  • 【RH134 问答题】第 11 章 管理网络安全
  • ACL 2024 大模型方向优秀论文:洞察NLP前沿​关键突破!
  • 前端框架Vue3(四)——组件通信及其他API
  • SecurityContextHolder 管理安全上下文的核心组件详解
  • python之使用ffmpeg下载直播推流视频rtmp、m3u8协议实时获取时间进度
  • 金融分类提示词演示
  • 代码随想录Day35:动态规划(背包问题 二维 一维、分割等和子集)
  • 守护金融核心业务 | 博睿数据《金融业务全景与全链路智能可观测体系建设白皮书》发布!
  • 云上服务器常见的存储方式和类型
  • MySQL 中的 JOIN 操作有哪些类型?它们之间有什么区别?
  • vk框架或者普通函数封装的一些函数可以拿取使用【会持续更新】
  • Maven模块化开发与设计笔记
  • 一起学springAI系列一:初体验
  • 解释 MySQL 中的 EXPLAIN 命令的作用和使用场景
  • 机器学习——互信息(超详细)
  • 机器学习基础-seaborn