Reflection Emit (反射發射) 是 .NET 運行時動態生成 IL 代碼、創建程序集與類型的強大機制。本文系統梳理 Emit 核心概念、標準六步流程,並以遞歸斐波那契數列為例,完整演示從 AppDomain 到 CreateType 的實戰過程。
屬於 System.Reflection 命名空間的高級功能。普通反射只能「檢測/調用」現有類型;Emit 允許運行時構建全新程序集、模塊、類型、方法,並生成 IL 指令流。典型應用:
AppDomain.DefineDynamicAssembly,指定 AssemblyBuilderAccess.RunAndSave。AssemblyBuilder.DefineDynamicModule,可指定輸出文件名。ModuleBuilder.DefineType,設置命名空間、類名、屬性 (TypeAttributes)。MethodBuilder.GetILGenerator() → ILGenerator.Emit。TypeBuilder.CreateType(),可選 AssemblyBuilder.Save 持久化。先寫 C# 目標代碼 → 編譯 → 反編譯查看標準 IL → 模仿發射。這比查閱 OpCodes 文檔高效得多。
public class Fibonacci
{
public int Calc(int num)
{
if (num == 1 || num == 2) return 1;
return Calc(num - 1) + Calc(num - 2);
}
}
Calc(num):
if num == 1: return 1
if num == 2: return 1
return Calc(num-1) + Calc(num-2)
var method = typeBuilder.DefineMethod("Calc",
MethodAttributes.Public, typeof(int), new[] { typeof(int) });
var il = method.GetILGenerator();
var lblReturn1 = il.DefineLabel();
var lblReturnResult = il.DefineLabel();
// if (num == 1) return 1
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Beq_S, lblReturn1);
// if (num == 2) return 1
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4_2);
il.Emit(OpCodes.Beq_S, lblReturn1);
// Calc(num-1)
il.Emit(OpCodes.Ldarg_0); // this
il.Emit(OpCodes.Ldarg_1); // num
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Call, method); // 遞歸調用自身
// Calc(num-2)
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4_2);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Call, method);
// add & return
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Br_S, lblReturnResult);
// label: return 1
il.MarkLabel(lblReturn1);
il.Emit(OpCodes.Ldc_I4_1);
// label: return result
il.MarkLabel(lblReturnResult);
il.Emit(OpCodes.Ret);
var type = typeBuilder.CreateType();
asmBuilder.Save("Fibonacci.dll");
var instance = Activator.CreateInstance(type);
var calcMethod = type.GetMethod("Calc");
for (int i = 1; i < 10; i++)
Console.WriteLine(calcMethod.Invoke(instance, new object[] { i }));
上例中 Activator.CreateInstance + MethodInfo.Invoke 仍有反射開銷。進階做法:再發一個靜態工廠方法返回強類型委托,或直接發射 DynamicMethod 綁定 Func<int, int>,實現零反射調用。
DynamicMethod、OpCodes、棧操作、局部變量、跳轉標簽。BeginExceptionBlock)、調用約定。Emit 學習曲線陡峭,但掌握後能解決反射性能瓶頸、實現框架級基礎設施,是 .NET 高階開發必備技能。