02-C#

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 类和方法都有泛型参数

两者需要分别处理,先 MakeGenericTypeMakeGenericMethod

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.LoadFromassembly.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)             // 设置字段值
相关推荐
啵啵鱼爱吃小猫咪2 小时前
机械臂阻抗控制github项目-mujoco仿真
开发语言·人工智能·python·机器人
oem1102 小时前
C++中的享元模式实战
开发语言·c++·算法
似水明俊德2 小时前
01-C#.Net-泛型-面试题
java·开发语言·面试·c#·.net
leonkay2 小时前
Golang语言闭包完全指南
开发语言·数据结构·后端·算法·架构·golang
Allnadyy2 小时前
【C++项目】从零实现高并发内存池(一):核心原理与设计思路
java·开发语言·jvm
雅欣鱼子酱2 小时前
Type-C供电PD协议取电Sink芯片ECP5702,可二端头分开供电调整亮度,适用于LED灯带户外防水超亮灯条方案
c语言·开发语言
似水明俊德3 小时前
07-C#
开发语言·c#
浩子智控3 小时前
python程序打包的文件地址处理
开发语言·python·pyqt
Jackey_Song_Odd3 小时前
Part 1:Python语言核心 - 序列与容器
开发语言·windows·python