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 反射方式
传统方式:
csharp
IDBHelper dBHelper = new SqlServerHelper();
dBHelper.Query();
缺点:代码与具体类强耦合,切换实现需要修改代码并重新编译。
反射方式(五步走):
csharp
// 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 关键字
csharp
// 使用 dynamic 可以避开编译器检查,运行时决定类型
dynamic dInstance = Activator.CreateInstance(type);
dInstance.Query(); // 正常
dInstance.NotExist(); // 编译通过,运行时报错
三、简单工厂模式 + 反射 + 配置文件
3.1 实现可配置的工厂
这是反射最核心的应用场景之一,也是 IOC 容器的雏形。
appsettings.json 中配置:
json
{
"ReflictionConfig": "Business.DB.Orcale.OrcaleHelper,Business.DB.Orcale.dll"
}
工厂类读取配置,动态创建对象:
csharp
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 到执行目录、修改配置文件,无需停止程序
四、反射的"黑科技"------破坏单例
反射可以访问私有构造函数,破坏单例模式:
csharp
// 正常单例使用
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[],按参数类型严格匹配:
csharp
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. 调用无参方法
csharp
object oInstance = Activator.CreateInstance(type);
MethodInfo show1 = type.GetMethod("Show1");
show1.Invoke(oInstance, null); // 或 new object[0]
b. 调用有参方法(处理重载)
重载方法同名,需要通过参数类型数组来区分:
csharp
// 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. 调用私有方法
csharp
MethodInfo show4 = type.GetMethod("Show4", BindingFlags.NonPublic | BindingFlags.Instance);
show4.Invoke(oInstance, new object[] { "参数" });
d. 调用静态方法
静态方法调用时,第一个参数(实例)传 null:
csharp
MethodInfo show5 = type.GetMethod("Show5");
show5.Invoke(null, new object[] { "参数" });
5.3 调用泛型方法
泛型是"延迟声明",调用时才确定类型。需要先用 MakeGenericMethod 确定类型参数:
csharp
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 确定类型:
csharp
// 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:
csharp
// 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 传统方式
csharp
People people = new People();
people.Id = 134;
people.Name = "张三";
people.Age = 25;
Console.WriteLine($"Id={people.Id}, Name={people.Name}");
6.2 反射方式
csharp
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 实现代码
csharp
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 使用示例
csharp
SqlServerHelper sqlServer = new SqlServerHelper();
SysCompany company = sqlServer.Find<SysCompany>(1);
SysUser user = sqlServer.Find<SysUser>(17);
前提条件 :实体类属性名必须与数据库列名完全一致,且实体类继承 BaseModel(保证有 Id 字段)。
7.4 优化:泛型缓存
每次调用 Find<T> 都重新生成 SQL 是浪费的,同一个类型的 SQL 结构是固定的。利用泛型类的静态构造函数实现缓存:
csharp
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}";
}
使用:
csharp
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 对象:
csharp
// 不推荐:每次都加载
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 基本使用
csharp
// 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 常用组合
csharp
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():运行时操作,返回对象实际运行时类型
csharp
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 容器
csharp
// 配置:接口 → 实现类映射
container.Register<IDBHelper, SqlServerHelper>();
// 使用:自动创建实例(底层是反射)
IDBHelper helper = container.Resolve<IDBHelper>();
11.3 ORM 框架
csharp
// 一个方法支持所有实体类型
var user = dbContext.Find<SysUser>(1);
var company = dbContext.Find<SysCompany>(1);
附录:常用 API 速查
Assembly 类
csharp
Assembly.LoadFrom(string) // 加载程序集
assembly.GetType(string) // 获取类型
assembly.GetTypes() // 获取所有类型
assembly.GetModules() // 获取所有模块
Type 类
csharp
type.GetConstructors() // 获取构造函数
type.GetMethods() // 获取方法
type.GetProperties() // 获取属性
type.GetFields() // 获取字段
type.GetMethod(name, types) // 获取指定重载方法
type.MakeGenericType(types) // 构造泛型类型
Activator 类
csharp
Activator.CreateInstance(type) // 创建实例(无参)
Activator.CreateInstance(type, args) // 创建实例(有参)
Activator.CreateInstance(type, true) // 访问私有构造
MethodInfo 类
csharp
method.Invoke(instance, parameters) // 调用方法
method.MakeGenericMethod(types) // 构造泛型方法
PropertyInfo / FieldInfo 类
csharp
prop.GetValue(instance) // 获取属性值
prop.SetValue(instance, value) // 设置属性值
field.GetValue(instance) // 获取字段值
field.SetValue(instance, value) // 设置字段值