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 类和方法都有泛型参数
两者需要分别处理,先MakeGenericType再MakeGenericMethod:
// 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.LoadFrom和assembly.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 / MethodBuilder9.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 加载程序集的三种方式
| 方法 | 参数 | 说明 |
|---|---|---|
LoadFrom | DLL 名称(需要后缀) | 最常用,支持相对路径,自动解析依赖 |
LoadFile | 完整路径(需要后缀) | 必须绝对路径,不自动解析依赖 |
Load | DLL 名称(不需要后缀) | 从 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) // 设置字段值