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

02-C#.Net-反射-学习笔记

一、反射基础概念

1.1 什么是反射?

反射(Reflection)是 C# 中的一项强大功能,来自System.Reflection命名空间。它允许程序在运行时动态地:

  • 读取程序集(DLL/EXE)的元数据(metadata)
  • 创建对象实例
  • 调用方法
  • 访问字段和属性

1.2 编译过程理解

高级语言(C#代码) ↓ 编译 DLL/EXE 文件 ├── Metadata(元数据):记录了 DLL 中包含的类型、方法等信息 └── IL(中间语言):编译后的中间代码,面向对象语言

反编译工具(如 ILSpy)也是利用同样的原理,把 dll 逆向还原成 C# 或 IL 代码。

1.3 反射的应用场景

  • 封装框架
  • 系统开发
  • MVC 框架(通过 URL 路由调用方法)
  • IOC 容器(依赖注入)
  • ORM 框架(对象关系映射)

二、反射创建对象

2.1 传统方式 vs 反射方式

传统方式:

IDBHelper dBHelper = new SqlServerHelper(); dBHelper.Query();

缺点:代码与具体类强耦合,切换实现需要修改代码并重新编译。

反射方式(五步走):

// 1. 动态加载 DLL(三种方式) Assembly assembly = Assembly.LoadFrom("Business.DB.SqlServer.dll"); // DLL 名称 // Assembly assembly = Assembly.LoadFile(@"完整路径\Business.DB.SqlServer.dll"); // 全路径 // Assembly assembly = Assembly.Load("Business.DB.SqlServer"); // 不需要后缀 // 2. 获取类型(需要类的全名称,含命名空间) Type type = assembly.GetType("Business.DB.SqlServer.SqlServerHelper"); // 3. 创建对象 object oInstance = Activator.CreateInstance(type); // 4. 类型转换(推荐使用 as,转换失败返回 null 而不是抛异常) IDBHelper helper = oInstance as IDBHelper; // 5. 调用方法 helper.Query();

为什么不能直接用oInstance.Query()

C# 是强类型语言,编译时以左边声明的类型为准。oInstance声明为object,编译器不知道它有Query方法,所以编译不通过。

2.2 dynamic 关键字

// 使用 dynamic 可以避开编译器检查,运行时决定类型 dynamic dInstance = Activator.CreateInstance(type); dInstance.Query(); // 正常 dInstance.NotExist(); // 编译通过,运行时报错

三、简单工厂模式 + 反射 + 配置文件

3.1 实现可配置的工厂

这是反射最核心的应用场景之一,也是 IOC 容器的雏形。

appsettings.json中配置:

{ "ReflictionConfig": "Business.DB.Orcale.OrcaleHelper,Business.DB.Orcale.dll" }

工厂类读取配置,动态创建对象:

public class SimpleFactory { public static IDBHelper CreateInstance() { string config = CustomConfigManager.GetConfig("ReflictionConfig"); // 格式:Business.DB.SqlServer.SqlServerHelper,Business.DB.SqlServer.dll string typeName = config.Split(',')[0]; string dllName = config.Split(',')[1]; Assembly assembly = Assembly.LoadFrom(dllName); Type type = assembly.GetType(typeName); object oInstance = Activator.CreateInstance(type); return oInstance as IDBHelper; } }

3.2 优势

  • 依赖倒置原则:依赖于抽象(接口)而非具体实现
  • 可配置:切换数据库只需修改配置文件,不需要改代码、不需要重新编译
  • 可扩展:添加新功能只需实现接口、复制 DLL 到执行目录、修改配置文件,无需停止程序

四、反射的"黑科技"——破坏单例

反射可以访问私有构造函数,破坏单例模式:

// 正常单例使用 Singleton singleton1 = Singleton.GetInstance(); Singleton singleton2 = Singleton.GetInstance(); Console.WriteLine(object.ReferenceEquals(singleton1, singleton2)); // True // 反射破坏单例(第二个参数 true 表示允许访问私有构造函数) Assembly assembly = Assembly.LoadFrom("Business.DB.SqlServer.dll"); Type type = assembly.GetType("Business.DB.SqlServer.Singleton"); Singleton s1 = (Singleton)Activator.CreateInstance(type, true); Singleton s2 = (Singleton)Activator.CreateInstance(type, true); Console.WriteLine(object.ReferenceEquals(s1, s2)); // False - 单例被破坏!

关键点:反射可以无视访问修饰符(private、protected 等)。访问修饰符是编译器层面的约束,反射在运行时直接操作元数据,完全绕过编译器检查。


五、反射调用构造函数和方法

5.1 调用带参数的构造函数

Activator.CreateInstance的第二个参数接收object[],按参数类型严格匹配:

Assembly assembly = Assembly.LoadFrom("Business.DB.SqlServer.dll"); Type type = assembly.GetType("Business.DB.SqlServer.ReflectionTest"); // 无参构造 object noParaObject = Activator.CreateInstance(type); // 有参构造(严格匹配参数类型,没有匹配的构造函数会抛异常) object paraObject1 = Activator.CreateInstance(type, new object[] { 123 }); // ReflectionTest(int) object paraObject2 = Activator.CreateInstance(type, new object[] { "张三" }); // ReflectionTest(string) object paraObject3 = Activator.CreateInstance(type, new object[] { 234, "李四" }); // ReflectionTest(int, string)

5.2 反射调用方法

a. 调用无参方法
object oInstance = Activator.CreateInstance(type); MethodInfo show1 = type.GetMethod("Show1"); show1.Invoke(oInstance, null); // 或 new object[0]
b. 调用有参方法(处理重载)

重载方法同名,需要通过参数类型数组来区分:

// Show3(string name, int id) MethodInfo show31 = type.GetMethod("Show3", new Type[] { typeof(string), typeof(int) }); show31.Invoke(oInstance, new object[] { "张三", 234 }); // Show3(int id) MethodInfo show32 = type.GetMethod("Show3", new Type[] { typeof(int) }); show32.Invoke(oInstance, new object[] { 345 });
c. 调用私有方法
MethodInfo show4 = type.GetMethod("Show4", BindingFlags.NonPublic | BindingFlags.Instance); show4.Invoke(oInstance, new object[] { "参数" });
d. 调用静态方法

静态方法调用时,第一个参数(实例)传null

MethodInfo show5 = type.GetMethod("Show5"); show5.Invoke(null, new object[] { "参数" });

5.3 调用泛型方法

泛型是"延迟声明",调用时才确定类型。需要先用MakeGenericMethod确定类型参数:

Type type = assembly.GetType("Business.DB.SqlServer.GenericMethod"); object oInstance = Activator.CreateInstance(type); MethodInfo show = type.GetMethod("Show"); // 确定泛型类型参数 MethodInfo genericShow = show.MakeGenericMethod(new Type[] { typeof(int), typeof(string), typeof(DateTime) }); genericShow.Invoke(oInstance, new object[] { 123, "张三", DateTime.Now });

5.4 创建泛型类实例

泛型类的类型名称后面要加反引号和参数数量,如GenericClass`3,然后用MakeGenericType确定类型:

// GenericClass<T, W, X> → "GenericClass`3" Type type = assembly.GetType("Business.DB.SqlServer.GenericClass`3"); Type genericType = type.MakeGenericType(new Type[] { typeof(int), typeof(string), typeof(DateTime) }); object oInstance = Activator.CreateInstance(genericType); MethodInfo show = genericType.GetMethod("Show"); show.Invoke(oInstance, new object[] { 123, "张三", DateTime.Now });

5.5 类和方法都有泛型参数

两者需要分别处理,先MakeGenericTypeMakeGenericMethod

// GenericDouble<T> 类 + Show<W, X> 方法 Type type = assembly.GetType("Business.DB.SqlServer.GenericDouble`1"); Type genericType = type.MakeGenericType(new Type[] { typeof(int) }); object oInstance = Activator.CreateInstance(genericType); MethodInfo show = genericType.GetMethod("Show"); MethodInfo genericMethod = show.MakeGenericMethod(new Type[] { typeof(string), typeof(DateTime) }); genericMethod.Invoke(oInstance, new object[] { 123, "张三", DateTime.Now });

六、反射操作属性和字段

6.1 传统方式

People people = new People(); people.Id = 134; people.Name = "张三"; people.Age = 25; Console.WriteLine($"Id={people.Id}, Name={people.Name}");

6.2 反射方式

Type type = typeof(People); object pObject = Activator.CreateInstance(type); // 设置属性值 foreach (var prop in type.GetProperties()) { if (prop.Name.Equals("Id")) prop.SetValue(pObject, 134); if (prop.Name.Equals("Name")) prop.SetValue(pObject, "张三"); if (prop.Name.Equals("Age")) prop.SetValue(pObject, 25); } // 获取所有属性值(不需要写死属性名) foreach (var prop in type.GetProperties()) { Console.WriteLine($"people.{prop.Name}={prop.GetValue(pObject)}"); } // 操作字段 foreach (var field in type.GetFields()) { Console.WriteLine($"{field.Name}={field.GetValue(pObject)}"); }

6.3 反射取值的优势

实体类新增属性时,反射代码无需修改,程序更稳定。传统方式则必须修改代码、重新编译发布。


七、手写 ORM 框架(反射 + 泛型)

7.1 核心思想

通过反射 + 泛型实现通用的数据库查询方法,一个方法支持所有实体类型。

7.2 实现代码

public T Find<T>(int id) where T : BaseModel { Type type = typeof(T); object oResult = Activator.CreateInstance(type); using (SqlConnection connection = new SqlConnection(ConnectionString)) { connection.Open(); // 反射动态生成 SQL:Select [Id],[Name],... from SysUser where id=1 string sql = $"Select {string.Join(',', type.GetProperties().Select(c => $"[{c.Name}]"))} from {type.Name} where id={id}"; SqlDataReader reader = new SqlCommand(sql, connection).ExecuteReader(); if (reader.Read()) { foreach (var prop in type.GetProperties()) { // DBNull 处理:数据库空值转为 null prop.SetValue(oResult, reader[prop.Name] is DBNull ? null : reader[prop.Name]); } } } return (T)oResult; }

7.3 使用示例

SqlServerHelper sqlServer = new SqlServerHelper(); SysCompany company = sqlServer.Find<SysCompany>(1); SysUser user = sqlServer.Find<SysUser>(17);

前提条件:实体类属性名必须与数据库列名完全一致,且实体类继承BaseModel(保证有Id字段)。

7.4 优化:泛型缓存

每次调用Find<T>都重新生成 SQL 是浪费的,同一个类型的 SQL 结构是固定的。利用泛型类的静态构造函数实现缓存:

public class ConstantSqlString<T> { private static string FindSql = null; // 静态构造函数:每个泛型类型 T 只执行一次(CLR 保证) static ConstantSqlString() { Type type = typeof(T); FindSql = $"Select {string.Join(',', type.GetProperties().Select(c => $"[{c.Name}]"))} from {type.Name} where id="; } public static string GetFindSql(int id) => $"{FindSql}{id}"; }

使用:

string sql = ConstantSqlString<T>.GetFindSql(id);

原理ConstantSqlString<SysUser>ConstantSqlString<SysCompany>是两个不同的类,各自的静态构造函数只执行一次,SQL 模板只生成一次,后续直接复用。


八、反射性能问题

8.1 性能测试结果

实测对比(循环 100 万次创建对象 + 调用方法):

方式耗时
普通 new~17 毫秒
反射(每次 LoadFrom)~6300 毫秒
反射(LoadFrom 缓存到循环外)~71 毫秒

8.2 性能优化建议

Assembly.LoadFromassembly.GetType提到循环外,只执行一次,后续复用Type对象:

// 不推荐:每次都加载 for (int i = 0; i < 1_000_000; i++) { Assembly assembly = Assembly.LoadFrom("Business.DB.SqlServer.dll"); Type type = assembly.GetType("Business.DB.SqlServer.SqlServerHelper"); object oInstance = Activator.CreateInstance(type); } // 推荐:缓存加载结果 Assembly assembly = Assembly.LoadFrom("Business.DB.SqlServer.dll"); Type dbHelperType = assembly.GetType("Business.DB.SqlServer.SqlServerHelper"); for (int i = 0; i < 1_000_000; i++) { object oInstance = Activator.CreateInstance(dbHelperType); IDBHelper helper = (IDBHelper)oInstance; helper.Query(); }

结论:反射经过缓存优化后,性能损耗是可以接受的,实际项目中可以放心使用。


九、Emit 技术(动态生成代码)

9.1 什么是 Emit?

Emit 是反射的进阶能力,可以在程序运行时动态生成 dll、类、方法、字段等,来自System.Reflection.Emit命名空间。

  • 反射是"读":读取已有程序集的元数据并使用它。
  • Emit 是"写":在运行时动态生成新的程序集和代码。

生成步骤(建造者模式):

AssemblyBuilder → ModuleBuilder → TypeBuilder → FieldBuilder / MethodBuilder

9.2 基本使用

// 1. 创建程序集 AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( new AssemblyName("DynamicAssemblyExample"), AssemblyBuilderAccess.RunAndCollect); // 2. 创建模块 ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MyModal"); // 3. 创建类型 TypeBuilder typeBuilder = moduleBuilder.DefineType("MyDynamicType", TypeAttributes.Public); // 4. 创建字段 FieldBuilder fieldBuilder = typeBuilder.DefineField("NumberField", typeof(int), FieldAttributes.Public); // 5. 创建构造函数,用 IL 写逻辑 ConstructorBuilder ctor = typeBuilder.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(int) }); ILGenerator ctorIL = ctor.GetILGenerator(); ctorIL.Emit(OpCodes.Ldarg_0); // 加载 this ctorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); ctorIL.Emit(OpCodes.Ldarg_0); // 加载 this ctorIL.Emit(OpCodes.Ldarg_1); // 加载参数 ctorIL.Emit(OpCodes.Stfld, fieldBuilder); // 赋值给字段 ctorIL.Emit(OpCodes.Ret); // 6. 创建方法 MethodBuilder method = typeBuilder.DefineMethod( "ConsoleMethod", MethodAttributes.Public | MethodAttributes.Static, null, null); ILGenerator methodIL = method.GetILGenerator(); methodIL.Emit(OpCodes.Ldstr, "欢迎来到高级班学习"); methodIL.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) })); methodIL.Emit(OpCodes.Ret); // IL 方法最后必须有 Ret // 7. 创建类型并使用 Type dynamicType = typeBuilder.CreateType(); object instance = Activator.CreateInstance(dynamicType, new object[] { 123456 }); MethodInfo consoleMethod = dynamicType.GetMethod("ConsoleMethod"); consoleMethod.Invoke(instance, null);

9.3 特点

  • 动态性强:可在运行时定义业务逻辑
  • 性能极高:生成的代码和手写代码一样快
  • 学习成本高:需要了解 IL 指令
  • 应用场景:动态代理、AOP 切面编程、高性能序列化等

十、反射核心知识点总结

10.1 加载程序集的三种方式

方法参数说明
LoadFromDLL 名称(需要后缀)最常用,支持相对路径,自动解析依赖
LoadFile完整路径(需要后缀)必须绝对路径,不自动解析依赖
LoadDLL 名称(不需要后缀)从 GAC 或应用程序目录加载

10.2 BindingFlags 常用组合

BindingFlags.Public | BindingFlags.Instance // 公共实例成员 BindingFlags.NonPublic | BindingFlags.Instance // 私有实例成员 BindingFlags.Public | BindingFlags.Static // 公共静态成员 BindingFlags.NonPublic | BindingFlags.Static // 私有静态成员

10.3 typeof(T) vs obj.GetType()

  • typeof(T):编译时操作,T 必须是已知类型名称
  • obj.GetType():运行时操作,返回对象实际运行时类型
IDBHelper helper = new SqlServerHelper(); typeof(IDBHelper) // → IDBHelper(接口类型) helper.GetType() // → SqlServerHelper(实际运行时类型)

10.4 反射的优缺点

优点:

  • 动态性:运行时决定行为
  • 灵活性:解耦具体实现
  • 可扩展性:无需修改代码即可扩展功能
  • 强大性:可访问私有成员

缺点:

  • 性能开销:比直接调用慢(可通过缓存优化)
  • 类型安全:编译时无法检查
  • 代码可读性:相对复杂

十一、实际应用场景

11.1 MVC 框架路由

URL: http://localhost/Home/Index ↓ 通过反射: 1. 根据 "Home" 创建 HomeController 实例 2. 根据 "Index" 调用 Index 方法

11.2 IOC 容器

// 配置:接口 → 实现类映射 container.Register<IDBHelper, SqlServerHelper>(); // 使用:自动创建实例(底层是反射) IDBHelper helper = container.Resolve<IDBHelper>();

11.3 ORM 框架

// 一个方法支持所有实体类型 var user = dbContext.Find<SysUser>(1); var company = dbContext.Find<SysCompany>(1);

附录:常用 API 速查

Assembly 类

Assembly.LoadFrom(string) // 加载程序集 assembly.GetType(string) // 获取类型 assembly.GetTypes() // 获取所有类型 assembly.GetModules() // 获取所有模块

Type 类

type.GetConstructors() // 获取构造函数 type.GetMethods() // 获取方法 type.GetProperties() // 获取属性 type.GetFields() // 获取字段 type.GetMethod(name, types) // 获取指定重载方法 type.MakeGenericType(types) // 构造泛型类型

Activator 类

Activator.CreateInstance(type) // 创建实例(无参) Activator.CreateInstance(type, args) // 创建实例(有参) Activator.CreateInstance(type, true) // 访问私有构造

MethodInfo 类

method.Invoke(instance, parameters) // 调用方法 method.MakeGenericMethod(types) // 构造泛型方法

PropertyInfo / FieldInfo 类

prop.GetValue(instance) // 获取属性值 prop.SetValue(instance, value) // 设置属性值 field.GetValue(instance) // 获取字段值 field.SetValue(instance, value) // 设置字段值
http://www.cnnetsun.cn/news/1366180.html

相关文章:

  • Java入门(String类)
  • Claude HUD:开发者的智能开发驾驶舱
  • 具身智能与人形机器人:大模型到底改变了什么?
  • Win10利用端口转发突破公网SMB访问限制
  • Alpamayo-R1-10B开源镜像免配置:预装AlpaSim模拟器的端到端研发环境
  • 高效配置AGENTS.md开发环境:3个提升AI编码代理工作效率的最佳实践
  • 【智能革命】信使Web Builder:当AI遇见可视化,如何重塑网站开发范式
  • 如何利用DuckDB查询缓存:提升重复查询性能的终极指南
  • Qwen3-ForcedAligner-0.6B在在线教育平台的集成案例
  • 微信小程序即时通讯模板:基于WebSocket的完整解决方案
  • 如何建立客观的h2ogpt模型评估体系:完整指南与实践技巧
  • FreeCAD:开源参数化3D建模软件的终极指南与深度解析
  • Transformer架构实战:从零理解自注意力机制到BERT/GPT实现差异
  • 3个关键问题:如何用TT-NN动态量化实现高效混合精度推理
  • Oni编辑器宏录制与回放:终极自动化重复任务指南
  • WAN2.2文生视频多场景落地:律所法律条款→情景剧式普法短视频自动生成
  • LSTM与RMBG-2.0结合的图像序列处理方案
  • Swin2SR快速部署指南:3步搭建个人图片修复工具
  • 终极指南:如何为你的Gridea静态博客构建坚不可摧的安全防线
  • RexUniNLU效果惊艳展示:中文长文本中嵌套事件与多跳关系精准抽取
  • 如何实现Giscus评论系统的实时数据同步:GitHub讨论更新与前端刷新机制详解
  • S12SD紫外线传感器原理与ESP32嵌入式集成指南
  • 7个关键指标!Walrus存储节点监控完整指南:确保去中心化存储高可用性
  • GPTs项目维护指南:5个关键策略确保长期可持续发展
  • TGN超参数调优终极指南:提升模型性能的10个关键技巧
  • 如何实现无障碍支持?Semi Design ARIA属性技术解析
  • Maccy更新失败解决指南:3种手动升级方法详解
  • 终极指南:如何使用pypdf提升PDF文档在Google中的排名
  • 如何在Robo 3T中配置MongoDB Atlas文本搜索索引:完整指南
  • 掌握ipatool日志系统:高效调试与问题追踪的完整指南