# C#反射与Emit动态编程的利器与性能代价分析## 目录1. 引言动态编程的诱惑与挑战2. 反射Reflection深度解析2.1 什么是反射2.2 反射的核心API与元数据访问2.3 反射的应用场景插件系统、ORM、序列化2.4 反射的性能瓶颈类型安全、缓存缺失、内存分配3. Emit 与 动态方法DynamicMethod3.1 从反射到动态生成为什么需要Emit3.2 认识IL中间语言3.3 Reflection.Emit 命名空间详解3.4 构建动态程序集、模块与类型4. 性能深度剖析基准测试与底层原理4.1 基准测试环境与工具4.2 直接调用 vs 反射 vs 委托 vs Emit vs 表达式树4.3 内存分配与GC压力分析5. 高级实战构建高性能动态代理5.1 实现一个简单的AOP框架5.2 动态对象的属性访问器PropertyAccessor5.3 对象映射器Mapper的性能优化6. 安全性与受限上下文7. 局限性与替代方案Source Generators 与 Native AOT8. 总结与最佳实践---## 1. 引言动态编程的诱惑与挑战在软件开发的世界里静态类型语言如C#因其类型安全、编译时检查和高性能而备受青睐。然而随着应用复杂度的增加我们经常遇到一些在编译时无法确定类型的场景例如一个插件系统需要在运行时加载用户提供的DLL一个ORM对象关系映射框架需要根据实体的属性动态生成SQL语句或者一个序列化库需要遍历对象的私有字段将其转换为JSON。在这些场景下如果采用硬编码的方式代码将变得臃肿且难以维护。C#为我们提供了两把利剑来应对这种动态需求**反射Reflection** 和 **Emit**。* **反射** 像是一面镜子允许我们在运行时窥探程序集的内部结构获取类型信息、成员信息并动态地调用方法或访问字段。它的优点是使用简单、直观是大多数动态需求的“第一选择”。* **Emit** 则更像是一门“炼金术”。它允许我们在运行时生成新的代码MSIL指令并将其编译为可执行的方法或类型。Emit 的学习曲线陡峭但它是打破性能瓶颈的关键。然而动态编程并非没有代价。反射虽然方便但其性能开销常常让开发者望而却步。Emit虽然性能卓越但其复杂的IL编码容易出错且难以调试。本文将深入探讨这两者的原理、性能差异以及在实际工程中的取舍。---## 2. 反射Reflection深度解析### 2.1 什么是反射反射是.NET框架提供的一组API位于 System.Reflection 命名空间下。它允许程序在运行时获取类型的元数据Metadata。元数据是“描述数据的数据”在.NET中它包含了类型的名称、可见性、基类、实现的接口、方法、属性、字段等信息。反射的核心能力包括1. **加载程序集**Assembly.Load 允许动态加载DLL。2. **类型发现**获取 Type 对象这是反射的入口。3. **成员访问**获取 MethodInfo, PropertyInfo, FieldInfo 等。4. **动态调用**通过 Invoke 或 SetValue 操作对象。### 2.2 反射的核心API与元数据访问让我们从一个基础的例子开始看看反射如何工作。假设我们有一个简单的类csharppublic class Calculator{private int _factor 2;public int Add(int a, int b){return a b;}public int Multiply(int a){return a * _factor;}private string GetSecret() Hidden;}#### 2.2.1 获取Type对象有几种方式获取 Typecsharp// 1. 通过实例Calculator calc new Calculator();Type typeByInstance calc.GetType();// 2. 通过typeof运算符编译时Type typeByTypeof typeof(Calculator);// 3. 通过字符串全名Type typeByString Type.GetType(ReflectionDemo.Calculator); // 需要包含命名空间#### 2.2.2 动态创建实例csharp// 无参构造object instance Activator.CreateInstance(typeof(Calculator));// 有参构造假设存在构造函数 public Calculator(int factor)// object instanceWithArgs Activator.CreateInstance(typeof(Calculator), new object[] { 5 });#### 2.2.3 调用方法csharp// 获取公共实例方法MethodInfo addMethod typeof(Calculator).GetMethod(Add);object result addMethod.Invoke(instance, new object[] { 10, 20 });Console.WriteLine($Add Result: {result}); // 输出 30// 调用私有方法需要指定BindingFlagsMethodInfo secretMethod typeof(Calculator).GetMethod(GetSecret, BindingFlags.NonPublic | BindingFlags.Instance);string secret secretMethod.Invoke(instance, null) as string;Console.WriteLine($Secret: {secret});#### 2.2.4 访问私有字段csharpFieldInfo factorField typeof(Calculator).GetField(_factor, BindingFlags.NonPublic | BindingFlags.Instance);int currentFactor (int)factorField.GetValue(instance);Console.WriteLine($Current Factor: {currentFactor});// 修改私有字段factorField.SetValue(instance, 10);Console.WriteLine($New Multiply Result: {(instance as Calculator).Multiply(5)}); // 输出 50### 2.3 反射的应用场景反射在实际框架中无处不在1. **ORM框架如Entity Framework**通过反射读取实体的属性名和类型映射到数据库表的列。2. **序列化如Newtonsoft.Json**反射获取对象的私有/公有字段递归读取值并写入JSON。3. **依赖注入容器**扫描程序集中的类型根据构造函数参数类型动态实例化对象。4. **插件架构**从目录加载DLL使用反射查找实现了特定接口的类型。### 2.4 反射的性能瓶颈尽管反射强大但它是有代价的。为什么反射这么慢1. **类型安全检查**反射涉及大量的类型校验。当调用 MethodInfo.Invoke 时CLR必须验证传入的参数数组与目标方法的签名是否匹配这比直接调用时由编译器进行的静态检查要慢得多。2. **缺乏内联优化**JITJust-In-Time编译器能够对直接调用的方法进行内联优化将方法体直接嵌入调用点消除方法调用的开销。反射调用是间接的JIT无法进行内联。3. **开销巨大的元数据查找**每次通过字符串获取 MethodInfo 时CLR都需要遍历该类型的元数据表进行字符串匹配哈希查找虽然快但依然存在开销。4. **参数装箱Boxing**Invoke 方法接收 object[] 参数。对于值类型如int、float传入时会发生装箱返回结果时也会发生拆箱。这导致了额外的内存分配和CPU消耗。5. **安全与访问检查**即使我们指定了 BindingFlagsCLR仍然会执行安全堆栈遍历Security Stack Walk和成员访问可见性检查。下面是一个简单的性能对比示例直观感受反射的开销csharp[MemoryDiagnoser]public class ReflectionBenchmark{private Calculator _calculator;private MethodInfo _addMethod;private Funcint, int, int _compiledDelegate;[GlobalSetup]public void Setup(){_calculator new Calculator();_addMethod typeof(Calculator).GetMethod(Add);// 预编译委托作为对比基准之一_compiledDelegate (Funcint, int, int)Delegate.CreateDelegate(typeof(Funcint, int, int), _calculator, _addMethod);}[Benchmark(Baseline true)]public int DirectCall() _calculator.Add(10, 20);[Benchmark]public int ReflectionCall() (int)_addMethod.Invoke(_calculator, new object[] { 10, 20 });[Benchmark]public int CompiledDelegateCall() _compiledDelegate(10, 20);}注实际运行结果通常显示反射调用比直接调用慢数十倍甚至上百倍而编译委托的开销几乎可以忽略不计这预示着Emit优化的方向。---## 3. Emit 与 动态方法DynamicMethod既然反射的主要性能瓶颈在于元数据查找、参数数组处理和安全检查那么如何既能动态调用又能拥有接近直接调用的性能呢答案是**生成并缓存专用的代码**。### 3.1 从反射到动态生成为什么需要EmitEmitSystem.Reflection.Emit允许我们在运行时动态生成中间语言IL指令。这些指令随后被JIT编译成本地代码。一旦生成后续调用将不再涉及反射查找而是直接调用这段“刚刚生成的”原生代码。这类似于C中的“模板元编程”或者JVM中的字节码生成如CGLib。在.NET生态中很多高性能框架如Newtonsoft.Json的早期版本、AutoMapper、Dapper都大量使用Emit来规避反射的性能问题。### 3.2 认识IL中间语言要使用Emit必须对MSILMicrosoft Intermediate Language有基本的了解。IL是一种面向对象的汇编语言。一个简单的 Add 方法 public int Add(int a, int b) a b; 对应的IL指令如下il.method public hidebysig instance int32 Add(int32 a, int32 b) cil managed{.maxstack 2ldarg.1 // 加载第一个参数 (a) 到堆栈ldarg.2 // 加载第二个参数 (b) 到堆栈add // 相加结果留在栈顶ret // 返回栈顶值}理解堆栈机模型是Emit编程的基础指令将操作数压入堆栈然后将结果弹出。### 3.3 Reflection.Emit 命名空间详解System.Reflection.Emit 包含几个核心类* **AssemblyBuilder**用于定义动态程序集。* **ModuleBuilder**用于定义模块。* **TypeBuilder**用于定义类、接口或结构体。* **MethodBuilder**用于定义方法。* **ILGenerator**用于生成IL指令码。* **DynamicMethod**一个轻量级的动态方法类适合生成单个方法不需要定义完整的程序集。大多数高性能优化场景中我们不需要创建完整的程序集使用 DynamicMethod 就足够了。### 3.4 构建动态程序集、模块与类型高级场景虽然 DynamicMethod 已经足够强大但有时候我们需要创建一个全新的类型例如AOP中的动态代理类这时就需要 AssemblyBuilder 和 TypeBuilder。#### 3.4.1 使用 DynamicMethod 创建高性能访问器让我们用 DynamicMethod 重写之前的字段访问目标是生成一个可以直接读写私有字段的委托性能媲美直接访问。csharppublic static class FieldAccessorGenerator{// 生成一个 FuncT, TField 类型的委托用于快速读取私有字段public static FuncT, TField BuildFieldGetterT, TField(string fieldName){// 获取字段信息用于获取字段Token和类型FieldInfo fieldInfo typeof(T).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);if (fieldInfo null)throw new ArgumentException($Field {fieldName} not found in type {typeof(T)});// 创建动态方法// 参数1: 方法名// 参数2: 返回类型// 参数3: 参数类型数组 (第一个参数是目标对象)// 参数4: 动态方法归属的类型 (设为目标类型可提升权限访问私有成员)DynamicMethod dynamicMethod new DynamicMethod($__get_{fieldName},typeof(TField),new Type[] { typeof(T) },typeof(T), // 限制动态方法的访问权限确保能访问私有字段true // skipVisibility 为 true通常设置为 true 以跳过 JIT 可见性检查);ILGenerator il dynamicMethod.GetILGenerator();// 加载参数 (this 指针)il.Emit(OpCodes.Ldarg_0);// 加载字段的值il.Emit(OpCodes.Ldfld, fieldInfo);// 返回il.Emit(OpCodes.Ret);// 创建委托return (FuncT, TField)dynamicMethod.CreateDelegate(typeof(FuncT, TField));}// 生成一个 ActionT, TField 类型的委托用于快速写入私有字段public static ActionT, TField BuildFieldSetterT, TField(string fieldName){FieldInfo fieldInfo typeof(T).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);if (fieldInfo null)throw new ArgumentException($Field {fieldName} not found in type {typeof(T)});DynamicMethod dynamicMethod new DynamicMethod($__set_{fieldName},typeof(void),new Type[] { typeof(T), typeof(TField) },typeof(T),true);ILGenerator il dynamicMethod.GetILGenerator();// 加载目标对象il.Emit(OpCodes.Ldarg_0);// 加载要设置的值il.Emit(OpCodes.Ldarg_1);// 存储字段il.Emit(OpCodes.Stfld, fieldInfo);// 返回 voidil.Emit(OpCodes.Ret);return (ActionT, TField)dynamicMethod.CreateDelegate(typeof(ActionT, TField));}}**使用示例**csharpclass Person{private int _age;public Person(int age) _age age;}// 获取 getter 和 settervar getAge FieldAccessorGenerator.BuildFieldGetterPerson, int(_age);var setAge FieldAccessorGenerator.BuildFieldSetterPerson, int(_age);Person p new Person(18);Console.WriteLine($Current Age: {getAge(p)}); // 18setAge(p, 25);Console.WriteLine($New Age: {getAge(p)}); // 25在这个例子中生成的IL代码逻辑非常简单仅仅是 ldarg.0 - ldfld - ret。JIT编译后这段代码的执行效率几乎等同于字段的直接访问非公有字段的访问权限检查在动态方法生成时已解决运行时无额外开销。#### 3.4.2 使用 TypeBuilder 创建动态类型AOP代理如果需要为一个类生成一个代理子类例如在方法执行前后添加日志则需要使用 TypeBuilder。csharppublic static class ProxyGenerator{public static T CreateProxyT(T target, Action before, Action after) where T : class{Type targetType typeof(T);// 定义程序集名称AssemblyName assemblyName new AssemblyName(DynamicProxyAssembly);// 定义程序集 (为了便于调试可设置为可保存)AssemblyBuilder assemblyBuilder AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);ModuleBuilder moduleBuilder assemblyBuilder.DefineDynamicModule(MainModule);// 定义类继承自 TTypeBuilder typeBuilder moduleBuilder.DefineType(${targetType.Name}Proxy,TypeAttributes.Public | TypeAttributes.Class,targetType);// 添加一个字段用于存储目标对象FieldBuilder targetField typeBuilder.DefineField(_target, targetType, FieldAttributes.Private);// 添加构造函数ConstructorBuilder ctorBuilder typeBuilder.DefineConstructor(MethodAttributes.Public,CallingConventions.Standard,new Type[] { targetType });ILGenerator ctorIL ctorBuilder.GetILGenerator();// 调用基类构造器ctorIL.Emit(OpCodes.Ldarg_0);ctorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));// 存储 targetctorIL.Emit(OpCodes.Ldarg_0);ctorIL.Emit(OpCodes.Ldarg_1);ctorIL.Emit(OpCodes.Stfld, targetField);ctorIL.Emit(OpCodes.Ret);// 重写所有虚方法简化起见仅处理无参方法foreach (var method in targetType.GetMethods(BindingFlags.Public | BindingFlags.Instance)){if (method.IsVirtual !method.IsFinal method.DeclaringType ! typeof(object)){MethodBuilder methodBuilder typeBuilder.DefineMethod(method.Name,MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.ReuseSlot,method.ReturnType,method.GetParameters().Select(p p.ParameterType).ToArray());ILGenerator methodIL methodBuilder.GetILGenerator();// 调用 before ActionmethodIL.Emit(OpCodes.Ldnull); // 对于闭包这里简化处理实际需要捕获 before/after 委托// 实际实现需要将 before/after 作为静态字段或构造函数参数传入// 调用原始方法methodIL.Emit(OpCodes.Ldarg_0);methodIL.Emit(OpCodes.Ldfld, targetField);for (int i 1; i method.GetParameters().Length; i){methodIL.Emit(OpCodes.Ldarg, i);}methodIL.Emit(OpCodes.Callvirt, method);methodIL.Emit(OpCodes.Ret);// 定义方法重写typeBuilder.DefineMethodOverride(methodBuilder, method);}}Type proxyType typeBuilder.CreateType();return (T)Activator.CreateInstance(proxyType, target);}}*注意上面的示例简化了闭包处理真实场景需要更复杂的设计如保存委托到静态字段或生成专门的方法。*---## 4. 性能深度剖析基准测试与底层原理### 4.1 基准测试环境与工具我们使用 BenchmarkDotNet 进行严格的基准测试。测试环境.NET 8.0, Windows 11, Intel Core i7-12700H。我们将比较以下五种调用方式的性能1. **直接调用**编译时确定作为基准线。2. **反射调用**MethodInfo.Invoke。3. **缓存委托**Delegate.CreateDelegate (本质上是反射生成委托但实际调用是直接调用委托)。4. **Emit 动态方法**DynamicMethod 生成的委托。5. **表达式树**Expression.Lambda.Compile (介于反射和Emit之间底层也使用Emit但API更友好)。#### 测试代码csharp[SimpleJob(RuntimeMoniker.Net80)][MemoryDiagnoser]public class PerformanceTests{private Calculator _calc;private MethodInfo _methodInfo;private Funcint, int, int _cachedDelegate;private Funcint, int, int _emitDelegate;private Funcint, int, int _expressionDelegate;[GlobalSetup]public void Setup(){_calc new Calculator();_methodInfo typeof(Calculator).GetMethod(Add);// 1. 缓存委托 (标准反射创建)_cachedDelegate (Funcint, int, int)Delegate.CreateDelegate(typeof(Funcint, int, int), _calc, _methodInfo);// 2. Emit 动态方法DynamicMethod dm new DynamicMethod(AddEmit, typeof(int), new[] { typeof(Calculator), typeof(int), typeof(int) }, typeof(Calculator));ILGenerator il dm.GetILGenerator();il.Emit(OpCodes.Ldarg_1);il.Emit(OpCodes.Ldarg_2);il.Emit(OpCodes.Add);il.Emit(OpCodes.Ret);_emitDelegate (Funcint, int, int)dm.CreateDelegate(typeof(Funcint, int, int));// 3. 表达式树var paramA Expression.Parameter(typeof(int), a);var paramB Expression.Parameter(typeof(int), b);var addExpr Expression.Add(paramA, paramB);var lambda Expression.LambdaFuncint, int, int(addExpr, paramA, paramB);_expressionDelegate lambda.Compile();}[Benchmark(Baseline true)]public int Direct() _calc.Add(10, 20);[Benchmark]public int Reflection() (int)_methodInfo.Invoke(_calc, new object[] { 10, 20 });[Benchmark]public int CachedDelegate() _cachedDelegate(10, 20);[Benchmark]public int Emit() _emitDelegate(10, 20);[Benchmark]public int Expression() _expressionDelegate(10, 20);}#### 预期结果分析典型值| Method | Mean | Ratio | Allocated || :--- | :--- | :--- | :--- || Direct | 0.2 ns | 1.00 | 0 B || CachedDelegate | 0.3 ns | 1.50 | 0 B || Emit | 0.3 ns | 1.50 | 0 B || Expression | 0.3 ns | 1.50 | 0 B || Reflection | 150.0 ns | 750.00 | 64 B (每次调用产生新数组) |**结论**1. **反射调用是最慢的**不仅速度慢约750倍差距而且每次调用都会分配新的 object[] 数组给GC带来压力。2. **缓存委托、Emit、表达式树**的性能非常接近直接调用。它们本质上都绕过了反射的Invoke机制直接通过委托指针调用原生代码。3. **表达式树**是一个极好的折中方案它提供了类似反射的易用性但底层在首次调用时编译为IL后续调用性能与Emit一致。### 4.2 内存分配与GC压力分析反射的内存分配主要体现在* **参数数组**每次 Invoke 都需要 new object[]。* **返回值装箱**如果返回的是值类型在返回 object 时会装箱。* **内部包装器**反射内部为了处理安全性和上下文也会产生额外的临时对象。相比之下Emit生成的委托没有任何额外的内存分配除了委托实例本身在首次创建时分配方法调用完全在栈上进行。---## 5. 高级实战构建高性能动态代理### 5.1 实现一个简单的AOP框架在成熟的AOPAspect Oriented Programming框架如 Castle DynamicProxy中Emit被用于生成代理类。下面我们实现一个简化的拦截器用于在方法执行前后添加日志。#### 目标我们希望在不修改原始类的情况下拦截 Add 方法记录输入和输出。#### 实现策略使用 TypeBuilder 生成一个派生类重写目标方法在调用 base 方法前后插入拦截逻辑。但这里我们换一种思路不通过派生类而是通过 DispatchProxy 配合 DynamicMethod或者直接生成一个包装类。为了展示Emit的复杂性我们直接生成一个实现了特定接口的类。csharppublic interface ICalculator{int Add(int a, int b);}public class RealCalculator : ICalculator{public int Add(int a, int b) a b;}public interface IInterceptor{void Before(string methodName, object[] args);void After(string methodName, object result);}public class LoggingInterceptor : IInterceptor{public void Before(string methodName, object[] args) Console.WriteLine($Before {methodName}, Args: {string.Join(,, args)});public void After(string methodName, object result) Console.WriteLine($After {methodName}, Result: {result});}public class ProxyGenerator{public static T CreateProxyT(T target, IInterceptor interceptor) where T : class{Type targetType typeof(T);// 定义动态程序集AssemblyBuilder assembly AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(DynamicProxyAssembly),AssemblyBuilderAccess.Run);ModuleBuilder module assembly.DefineDynamicModule(DynamicProxyModule);// 定义类名TypeBuilder typeBuilder module.DefineType(${targetType.Name}Proxy,TypeAttributes.Public | TypeAttributes.Class,typeof(object), // 基类new Type[] { targetType } // 实现目标接口);// 字段: 目标对象和拦截器FieldBuilder targetField typeBuilder.DefineField(_target, targetType, FieldAttributes.Private);FieldBuilder interceptorField typeBuilder.DefineField(_interceptor, typeof(IInterceptor), FieldAttributes.Private);// 构造函数ConstructorBuilder ctor typeBuilder.DefineConstructor(MethodAttributes.Public,CallingConventions.Standard,new Type[] { targetType, typeof(IInterceptor) });ILGenerator ctorIL ctor.GetILGenerator();ctorIL.Emit(OpCodes.Ldarg_0);ctorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));ctorIL.Emit(OpCodes.Ldarg_0);ctorIL.Emit(OpCodes.Ldarg_1);ctorIL.Emit(OpCodes.Stfld, targetField);ctorIL.Emit(OpCodes.Ldarg_0);ctorIL.Emit(OpCodes.Ldarg_2);ctorIL.Emit(OpCodes.Stfld, interceptorField);ctorIL.Emit(OpCodes.Ret);// 实现接口的所有方法foreach (var method in targetType.GetMethods()){if (method.IsAbstract || method.IsVirtual){MethodBuilder methodBuilder typeBuilder.DefineMethod(method.Name,MethodAttributes.Public | MethodAttributes.Virtual,method.ReturnType,method.GetParameters().Select(p p.ParameterType).ToArray());ILGenerator methodIL methodBuilder.GetILGenerator();// 准备参数数组 (用于Before/After)ParameterInfo[] parameters method.GetParameters();// 创建 object[] 数组methodIL.Emit(OpCodes.Ldc_I4, parameters.Length);methodIL.Emit(OpCodes.Newarr, typeof(object));for (int i 0; i parameters.Length; i){methodIL.Emit(OpCodes.Dup);methodIL.Emit(OpCodes.Ldc_I4, i);methodIL.Emit(OpCodes.Ldarg, i 1); // 1 因为 this 是 arg0if (parameters[i].ParameterType.IsValueType)methodIL.Emit(OpCodes.Box, parameters[i].ParameterType);methodIL.Emit(OpCodes.Stelem_Ref);}// 存储参数数组局部变量 (索引 0)LocalBuilder argsArray methodIL.DeclareLocal(typeof(object[]));methodIL.Emit(OpCodes.Stloc, argsArray);// 调用 BeforemethodIL.Emit(OpCodes.Ldarg_0);methodIL.Emit(OpCodes.Ldfld, interceptorField);methodIL.Emit(OpCodes.Ldstr, method.Name);methodIL.Emit(OpCodes.Ldloc, argsArray);methodIL.Emit(OpCodes.Callvirt, typeof(IInterceptor).GetMethod(Before));// 调用目标方法methodIL.Emit(OpCodes.Ldarg_0);methodIL.Emit(OpCodes.Ldfld, targetField);for (int i 0; i parameters.Length; i){methodIL.Emit(OpCodes.Ldarg, i 1);}methodIL.Emit(OpCodes.Callvirt, method);// 存储返回值如果是值类型需要装箱但这里简化处理LocalBuilder resultLocal null;if (method.ReturnType ! typeof(void)){resultLocal methodIL.DeclareLocal(method.ReturnType);methodIL.Emit(OpCodes.Stloc, resultLocal);}// 调用 After (这里为了展示返回值简单处理)methodIL.Emit(OpCodes.Ldarg_0);methodIL.Emit(OpCodes.Ldfld, interceptorField);methodIL.Emit(OpCodes.Ldstr, method.Name);if (method.ReturnType typeof(void)){methodIL.Emit(OpCodes.Ldnull);}else{methodIL.Emit(OpCodes.Ldloc, resultLocal);if (method.ReturnType.IsValueType)methodIL.Emit(OpCodes.Box, method.ReturnType);}methodIL.Emit(OpCodes.Callvirt, typeof(IInterceptor).GetMethod(After));// 返回结果if (method.ReturnType typeof(void)){methodIL.Emit(OpCodes.Ret);}else{methodIL.Emit(OpCodes.Ldloc, resultLocal);methodIL.Emit(OpCodes.Ret);}}}Type proxyType typeBuilder.CreateType();return (T)Activator.CreateInstance(proxyType, target, interceptor);}}// 使用var real new RealCalculator();var proxy ProxyGenerator.CreateProxyICalculator(real, new LoggingInterceptor());int result proxy.Add(5, 3);Console.WriteLine($Final: {result});这个例子展示了Emit的强大能力我们生成了一个全新的类它在运行时动态插入了横切逻辑。虽然代码较为复杂但它展现的性能是无可比拟的。### 5.2 动态对象的属性访问器PropertyAccessor在ORM或序列化中频繁访问属性是常见需求。使用反射的 PropertyInfo.GetValue 和 SetValue 很慢。我们可以用Emit生成属性访问委托。csharppublic static class PropertyAccessor{public static FuncT, TProp BuildGetterT, TProp(string propertyName){PropertyInfo prop typeof(T).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);if (prop null) throw new ArgumentException(Property not found);DynamicMethod dm new DynamicMethod($get_{propertyName}, typeof(TProp), new[] { typeof(T) }, typeof(T), true);ILGenerator il dm.GetILGenerator();il.Emit(OpCodes.Ldarg_0);il.Emit(OpCodes.Callvirt, prop.GetMethod);if (prop.PropertyType.IsValueType typeof(TProp) typeof(object)){il.Emit(OpCodes.Box, prop.PropertyType);}il.Emit(OpCodes.Ret);return (FuncT, TProp)dm.CreateDelegate(typeof(FuncT, TProp));}}### 5.3 对象映射器Mapper的性能优化类似于AutoMapper对象映射通常涉及遍历源对象的属性并赋值给目标对象。使用反射的映射器性能堪忧而使用Emit生成映射代码可以达到接近手写映射的性能。csharppublic static class MapperGenerator{public static ActionTSource, TTarget BuildMapperTSource, TTarget(){DynamicMethod dm new DynamicMethod(Map, typeof(void), new[] { typeof(TSource), typeof(TTarget) }, typeof(MapperGenerator), true);ILGenerator il dm.GetILGenerator();var sourceProps typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p p.CanRead);var targetProps typeof(TTarget).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p p.CanWrite).ToDictionary(p p.Name, p p);foreach (var sourceProp in sourceProps){if (targetProps.TryGetValue(sourceProp.Name, out var targetProp) targetProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType)){// 加载源对象il.Emit(OpCodes.Ldarg_1); // targetil.Emit(OpCodes.Ldarg_0); // sourceil.Emit(OpCodes.Callvirt, sourceProp.GetMethod);// 处理值类型装箱这里假设类型兼容il.Emit(OpCodes.Callvirt, targetProp.SetMethod);}}il.Emit(OpCodes.Ret);return (ActionTSource, TTarget)dm.CreateDelegate(typeof(ActionTSource, TTarget));}}---## 6. 安全性与受限上下文使用Emit需要谨慎处理安全问题。动态生成的代码运行在当前的 Assembly 上下文中。* **透明度**动态方法可以指定 skipVisibility 参数。如果设为 true动态方法可以访问私有成员这类似于 ReflectionPermission 的提升权限。在部分信任环境如ASP.NET Core 早期版本中可能受到限制。* **验证**生成的IL必须是“可验证”的否则JIT可能会抛出 VerificationException。不安全的类型转换、栈溢出等问题都会导致运行时错误。可以使用 PEVerify 工具检查生成的程序集。**最佳实践**在生成Emit代码时确保类型安全尽量生成可验证的代码。如果只是为了绕过私有成员访问确保你的应用有足够的信任级别。---## 7. 局限性与替代方案Source Generators 与 Native AOT随着.NET生态的发展动态代码生成面临新的挑战。### 7.1 Source Generators在.NET 5中引入的 **Source Generators** 是一种在编译时生成代码的技术。它通过分析源代码在编译阶段生成C#代码。**优势**1. **零运行时开销**生成的代码在编译时就被整合进程序集运行时无需Emit或反射。2. **调试友好**生成的C#代码可以被调试。3. **AOT友好**在Native AOTAhead-of-Time Compilation场景下无法动态生成代码因为运行时没有JIT。Source Generators成为高性能动态场景的唯一出路。**适用场景*** JSON序列化System.Text.Json 的 Source Generator* ORM的实体映射* 依赖注入容器**示例**使用Source Generator实现简单映射需创建单独的生成器项目较复杂此处仅示意优势csharp// 生成的代码public static partial class Mapper{public static partial UserDto MapToDto(User user);}### 7.2 Native AOTNative AOT 将应用直接编译为原生代码启动时无需JIT。这意味着 System.Reflection.Emit 将无法工作因为运行时不包含JIT编译器。如果计划发布Native AOT应用必须避免使用Emit转而使用Source Generators或静态反射System.Reflection.Metadata 的只读元数据访问。---## 8. 总结与最佳实践C#反射与Emit是动态编程的两大支柱它们各自有鲜明的特点和适用场景。### 何时使用反射* **开发效率优先**反射API简单直观适合快速原型开发。* **低频调用**如果动态操作只在初始化时执行例如IoC容器注册反射的开销可以忽略。* **工具类**在Visual Studio扩展、代码分析工具等场景反射的便利性大于性能开销。### 何时使用Emit* **高频调用**在序列化、ORM、RPC框架的核心路径上每一次调用都需要极致性能。* **需要绕过反射开销**当你发现反射导致的GC压力成为瓶颈时。* **实现AOP**需要动态生成代理类。### 最佳实践总结1. **优先考虑表达式树**如果Emit的IL编写太复杂但需要比反射更好的性能表达式树是最好的折中方案。Expression.Lambda.Compile() 底层使用Emit但提供了类型安全的API。2. **缓存动态生成的委托**无论使用哪种技术确保将生成的委托Func/Action缓存起来避免重复生成IL的开销。3. **考虑Source Generators**对于新的库开发特别是针对.NET 8Source Generators通常是比Emit更好的选择尤其是需要支持Native AOT时。4. **验证IL**在调试动态方法时使用 DynamicMethod.Save需开启非托管调试或者将动态程序集保存到磁盘利用 ILDasm 查看生成的IL代码是否正确。5. **内存安全**注意Emit生成的代码不会被GC回收只要委托还在引用。设计上避免无限制地生成动态类型如类型爆炸这会导致内存泄漏。反射给了我们运行时审视和操作代码的能力而Emit赋予了我们运行时创造代码的魔法。理解并正确运用这两者是迈向高级.NET开发者的必经之路。在追求极致性能的同时也不要忘记代码的可维护性在“动态”与“稳定”之间找到最适合项目需求的平衡点。