别只会反射:C#.NET Emit 动态生成代码实战详解

简介

Emit 通常指的是:

csharp 复制代码
System.Reflection.Emit

它是 .NET 里一套非常底层的运行时代码生成 API。

一句话概括:

text 复制代码
Emit 可以在程序运行时动态生成 IL、方法、类型、属性、字段和程序集。

普通 C# 代码是先写源码,再编译成程序集,然后运行。

Emit 的思路不一样:

text 复制代码
运行时
  |
  v
动态拼 IL 指令
  |
  v
生成方法或类型
  |
  v
直接调用

这听起来有点像"运行时手写 IL"。

实际也差不多。

Emit 能解决什么问题?

Emit 不是日常业务代码的首选工具。

它更常见于框架底层和性能敏感场景。

典型场景有这些:

  • ORM 把数据库行快速映射成对象
  • JSON 序列化器生成属性访问代码
  • Mapper 框架生成对象复制代码
  • AOP / 动态代理框架生成代理类型
  • Mock 框架运行时生成接口实现
  • 表达式引擎把公式编译成方法
  • 替代高频反射调用,减少性能损耗

比如一个 ORM 要把查询结果填充到对象:

csharp 复制代码
user.Id = reader.GetInt32(0);
user.Name = reader.GetString(1);

如果每次都用反射:

csharp 复制代码
property.SetValue(user, value);

灵活是灵活,但频繁调用时开销明显。

Emit 可以在运行时生成类似手写赋值的代码,然后缓存成委托反复调用。

这样第一次生成稍微复杂一点,后面调用就很快。

Emit 和反射的区别

反射偏"运行时读取和调用已有结构"。

Emit 偏"运行时生成新的结构或新的执行逻辑"。

对比项 反射 Emit
主要作用 读取类型信息、调用已有成员 动态生成 IL、方法、类型
使用难度 较低 较高
性能 直接调用较慢 生成委托后通常更快
可读性 较好 较差
适合场景 配置、扫描、插件、低频调用 框架底层、高频调用、动态代理

简单理解:

text 复制代码
反射是查字典和照着调用。
Emit 是现场生成一段更接近手写代码的执行逻辑。

Emit 和表达式树、源生成器的区别

这几个东西都和"生成代码"有关,但时间点和使用方式不同。

技术 生成时间 生成内容 常见用途
Emit 运行时 IL / 动态类型 / 动态方法 动态代理、运行时优化
Expression Tree 运行时 表达式树,可编译成委托 动态查询、动态委托
Source Generator 编译期 C# 源码 静态代码生成、AOT 友好

如果需求在编译期就能确定,源生成器通常更稳。

如果需求必须运行时才知道,比如运行时拿到类型、字段、接口,再临时生成方法或代理,Emit 仍然有价值。

Emit 的核心类

常用类型如下:

类型 作用
DynamicMethod 在内存中创建一个轻量级动态方法
ILGenerator 发出 IL 指令
AssemblyBuilder 创建动态程序集
ModuleBuilder 创建动态模块
TypeBuilder 创建动态类型
FieldBuilder 创建字段
PropertyBuilder 创建属性
MethodBuilder 创建方法
ConstructorBuilder 创建构造函数

如果只是生成一个临时方法,优先看 DynamicMethod

如果要生成一个完整类、属性、接口实现,就要用:

text 复制代码
AssemblyBuilder
ModuleBuilder
TypeBuilder
MethodBuilder
ILGenerator

IL 是栈式执行模型

Emit 最难的地方不是 API 多,而是要理解 IL 的栈模型。

例如要生成这个方法:

csharp 复制代码
int Add(int a, int b)
{
    return a + b;
}

对应 IL 思路是:

text 复制代码
把 a 压栈
把 b 压栈
弹出两个值相加
把结果压栈
返回栈顶结果

对应指令:

csharp 复制代码
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);

常见指令先记这些就够用:

指令 作用
Ldarg_0 加载第 0 个参数,实例方法里通常是 this
Ldarg_1 加载第 1 个参数
Ldstr 加载字符串常量
Ldfld 读取实例字段
Stfld 写入实例字段
Call 调用静态方法或非虚方法
Callvirt 调用虚方法或实例方法
Castclass 引用类型转换
Box 值类型装箱
Unbox_Any 拆箱成值类型
Newobj 调用构造函数创建对象
Ret 返回

Demo 目标

下面写一个控制台 Demo,覆盖几个真实场景:

text 复制代码
动态生成加法方法
动态生成属性 getter
动态生成属性 setter
动态生成对象映射器
动态生成一个类
动态生成一个接口实现

创建项目:

bash 复制代码
mkdir EmitDemo
cd EmitDemo

dotnet new console

修改 Program.cs

准备实体类

先准备两个普通类型:

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

Console.WriteLine("Emit Demo");

public class User
{
    public int Id { get; set; }

    public string Name { get; set; } = "";

    public int Age { get; set; }
}

public class UserDto
{
    public int Id { get; set; }

    public string Name { get; set; } = "";
}

后面的示例都基于这两个类。

DynamicMethod:动态生成加法方法

先从最小例子开始。

目标是运行时生成一个方法:

csharp 复制代码
int Add(int a, int b)
{
    return a + b;
}

Emit 代码:

csharp 复制代码
static Func<int, int, int> CreateAdd()
{
    DynamicMethod method = new(
        name: "Add",
        returnType: typeof(int),
        parameterTypes: new[] { typeof(int), typeof(int) },
        m: typeof(User).Module);

    ILGenerator il = method.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    return method.CreateDelegate<Func<int, int, int>>();
}

调用:

csharp 复制代码
Func<int, int, int> add = CreateAdd();

Console.WriteLine(add(10, 20));

输出:

text 复制代码
30

这段代码的关键不是加法,而是理解 Emit 的基本流程:

text 复制代码
创建 DynamicMethod
获取 ILGenerator
逐条发出 IL 指令
CreateDelegate 转成可调用委托

动态生成属性 Getter

接下来做一个更贴近实际项目的例子。

目标是根据类型和属性名,动态生成类似下面的方法:

csharp 复制代码
object? GetValue(object instance)
{
    return ((User)instance).Name;
}

Emit 实现:

csharp 复制代码
static Func<object, object?> CreateGetter(Type type, string propertyName)
{
    PropertyInfo property = type.GetProperty(propertyName)
        ?? throw new InvalidOperationException($"属性不存在:{propertyName}");

    MethodInfo getMethod = property.GetGetMethod()
        ?? throw new InvalidOperationException($"属性没有 getter:{propertyName}");

    DynamicMethod method = new(
        name: "Get_" + type.Name + "_" + propertyName,
        returnType: typeof(object),
        parameterTypes: new[] { typeof(object) },
        m: type.Module,
        skipVisibility: true);

    ILGenerator il = method.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Castclass, type);
    il.Emit(OpCodes.Callvirt, getMethod);

    if (property.PropertyType.IsValueType)
    {
        il.Emit(OpCodes.Box, property.PropertyType);
    }

    il.Emit(OpCodes.Ret);

    return method.CreateDelegate<Func<object, object?>>();
}

调用:

csharp 复制代码
var user = new User
{
    Id = 1,
    Name = "张三",
    Age = 20
};

Func<object, object?> getName = CreateGetter(typeof(User), "Name");
Func<object, object?> getAge = CreateGetter(typeof(User), "Age");

Console.WriteLine(getName(user));
Console.WriteLine(getAge(user));

输出:

text 复制代码
张三
20

这里有一个关键点:

csharp 复制代码
if (property.PropertyType.IsValueType)
{
    il.Emit(OpCodes.Box, property.PropertyType);
}

因为委托返回的是 object?

如果属性是 intDateTime 这种值类型,需要装箱成 object

动态生成属性 Setter

Getter 能读,Setter 负责写。

目标是动态生成类似下面的方法:

csharp 复制代码
void SetValue(object instance, object? value)
{
    ((User)instance).Age = (int)value!;
}

Emit 实现:

csharp 复制代码
static Action<object, object?> CreateSetter(Type type, string propertyName)
{
    PropertyInfo property = type.GetProperty(propertyName)
        ?? throw new InvalidOperationException($"属性不存在:{propertyName}");

    MethodInfo setMethod = property.GetSetMethod()
        ?? throw new InvalidOperationException($"属性没有 setter:{propertyName}");

    DynamicMethod method = new(
        name: "Set_" + type.Name + "_" + propertyName,
        returnType: typeof(void),
        parameterTypes: new[] { typeof(object), typeof(object) },
        m: type.Module,
        skipVisibility: true);

    ILGenerator il = method.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Castclass, type);

    il.Emit(OpCodes.Ldarg_1);

    if (property.PropertyType.IsValueType)
    {
        il.Emit(OpCodes.Unbox_Any, property.PropertyType);
    }
    else
    {
        il.Emit(OpCodes.Castclass, property.PropertyType);
    }

    il.Emit(OpCodes.Callvirt, setMethod);
    il.Emit(OpCodes.Ret);

    return method.CreateDelegate<Action<object, object?>>();
}

调用:

csharp 复制代码
Action<object, object?> setName = CreateSetter(typeof(User), "Name");
Action<object, object?> setAge = CreateSetter(typeof(User), "Age");

setName(user, "李四");
setAge(user, 28);

Console.WriteLine(user.Name);
Console.WriteLine(user.Age);

输出:

text 复制代码
李四
28

Setter 里要注意拆箱:

csharp 复制代码
il.Emit(OpCodes.Unbox_Any, property.PropertyType);

因为传进来的 valueobject,但真正写入 Age 时需要 int

实战:动态生成对象映射器

很多 Mapper 框架都会做类似的事情:

csharp 复制代码
User -> UserDto

手写代码是这样:

csharp 复制代码
var dto = new UserDto
{
    Id = user.Id,
    Name = user.Name
};

下面用 Emit 生成一个映射委托:

csharp 复制代码
Func<User, UserDto> mapper = CreateMapper<User, UserDto>();

UserDto dto = mapper(new User
{
    Id = 100,
    Name = "王五",
    Age = 30
});

Console.WriteLine($"{dto.Id} - {dto.Name}");

输出:

text 复制代码
100 - 王五

实现代码:

csharp 复制代码
static Func<TSource, TTarget> CreateMapper<TSource, TTarget>()
    where TTarget : new()
{
    Type sourceType = typeof(TSource);
    Type targetType = typeof(TTarget);

    DynamicMethod method = new(
        name: "Map_" + sourceType.Name + "_To_" + targetType.Name,
        returnType: targetType,
        parameterTypes: new[] { sourceType },
        m: targetType.Module,
        skipVisibility: true);

    ILGenerator il = method.GetILGenerator();

    LocalBuilder targetLocal = il.DeclareLocal(targetType);

    ConstructorInfo ctor = targetType.GetConstructor(Type.EmptyTypes)
        ?? throw new InvalidOperationException($"{targetType.Name} 缺少无参构造函数");

    il.Emit(OpCodes.Newobj, ctor);
    il.Emit(OpCodes.Stloc, targetLocal);

    foreach (PropertyInfo targetProperty in targetType.GetProperties())
    {
        if (!targetProperty.CanWrite)
        {
            continue;
        }

        PropertyInfo? sourceProperty = sourceType.GetProperty(targetProperty.Name);
        if (sourceProperty is null || !sourceProperty.CanRead)
        {
            continue;
        }

        if (sourceProperty.PropertyType != targetProperty.PropertyType)
        {
            continue;
        }

        MethodInfo getMethod = sourceProperty.GetGetMethod()
            ?? throw new InvalidOperationException($"源属性没有 getter:{sourceProperty.Name}");

        MethodInfo setMethod = targetProperty.GetSetMethod()
            ?? throw new InvalidOperationException($"目标属性没有 setter:{targetProperty.Name}");

        il.Emit(OpCodes.Ldloc, targetLocal);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Callvirt, getMethod);
        il.Emit(OpCodes.Callvirt, setMethod);
    }

    il.Emit(OpCodes.Ldloc, targetLocal);
    il.Emit(OpCodes.Ret);

    return method.CreateDelegate<Func<TSource, TTarget>>();
}

这段 IL 的核心逻辑是:

text 复制代码
new UserDto()
保存到局部变量
读取 source.Id
写入 target.Id
读取 source.Name
写入 target.Name
返回 target

这种映射器通常会缓存起来:

csharp 复制代码
private static readonly Func<User, UserDto> CachedMapper = CreateMapper<User, UserDto>();

不要每次映射都重新 Emit。

正确方式是:

text 复制代码
第一次生成委托
缓存委托
后续重复调用委托

生成完整动态类型

DynamicMethod 适合生成单个方法。

如果要生成一个类,就要用 AssemblyBuilderModuleBuilderTypeBuilder

目标是运行时生成这个类:

csharp 复制代码
public class RuntimePerson
{
    private string _name;

    public RuntimePerson(string name)
    {
        _name = name;
    }

    public string Name
    {
        get => _name;
        set => _name = value;
    }

    public string SayHello()
    {
        return "Hello " + _name;
    }
}

Emit 代码:

csharp 复制代码
static Type CreatePersonType()
{
    AssemblyName assemblyName = new("RuntimePersonAssembly");

    AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
        assemblyName,
        AssemblyBuilderAccess.RunAndCollect);

    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");

    TypeBuilder typeBuilder = moduleBuilder.DefineType(
        "RuntimePerson",
        TypeAttributes.Public | TypeAttributes.Class);

    FieldBuilder nameField = typeBuilder.DefineField(
        "_name",
        typeof(string),
        FieldAttributes.Private);

    ConstructorBuilder constructor = typeBuilder.DefineConstructor(
        MethodAttributes.Public,
        CallingConventions.Standard,
        new[] { typeof(string) });

    ILGenerator ctorIl = constructor.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, nameField);
    ctorIl.Emit(OpCodes.Ret);

    PropertyBuilder nameProperty = typeBuilder.DefineProperty(
        "Name",
        PropertyAttributes.None,
        typeof(string),
        Type.EmptyTypes);

    MethodBuilder getName = typeBuilder.DefineMethod(
        "get_Name",
        MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
        typeof(string),
        Type.EmptyTypes);

    ILGenerator getIl = getName.GetILGenerator();
    getIl.Emit(OpCodes.Ldarg_0);
    getIl.Emit(OpCodes.Ldfld, nameField);
    getIl.Emit(OpCodes.Ret);

    MethodBuilder setName = typeBuilder.DefineMethod(
        "set_Name",
        MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
        typeof(void),
        new[] { typeof(string) });

    ILGenerator setIl = setName.GetILGenerator();
    setIl.Emit(OpCodes.Ldarg_0);
    setIl.Emit(OpCodes.Ldarg_1);
    setIl.Emit(OpCodes.Stfld, nameField);
    setIl.Emit(OpCodes.Ret);

    nameProperty.SetGetMethod(getName);
    nameProperty.SetSetMethod(setName);

    MethodBuilder sayHello = typeBuilder.DefineMethod(
        "SayHello",
        MethodAttributes.Public,
        typeof(string),
        Type.EmptyTypes);

    ILGenerator sayIl = sayHello.GetILGenerator();
    sayIl.Emit(OpCodes.Ldstr, "Hello ");
    sayIl.Emit(OpCodes.Ldarg_0);
    sayIl.Emit(OpCodes.Ldfld, nameField);
    sayIl.Emit(OpCodes.Call, typeof(string).GetMethod(
        nameof(string.Concat),
        new[] { typeof(string), typeof(string) })!);
    sayIl.Emit(OpCodes.Ret);

    return typeBuilder.CreateType()
        ?? throw new InvalidOperationException("动态类型创建失败");
}

调用:

csharp 复制代码
Type personType = CreatePersonType();

object person = Activator.CreateInstance(personType, "赵六")
    ?? throw new InvalidOperationException("实例创建失败");

Console.WriteLine(personType.GetProperty("Name")!.GetValue(person));

personType.GetProperty("Name")!.SetValue(person, "钱七");

Console.WriteLine(personType.GetMethod("SayHello")!.Invoke(person, null));

输出:

text 复制代码
赵六
Hello 钱七

动态实现接口

Emit 也可以运行时生成接口实现。

先准备接口:

csharp 复制代码
public interface ICalculator
{
    int Add(int a, int b);
}

目标是动态生成一个类:

csharp 复制代码
public class RuntimeCalculator : ICalculator
{
    public int Add(int a, int b)
    {
        Console.WriteLine("Add invoked");
        return a + b;
    }
}

Emit 实现:

csharp 复制代码
static Type CreateCalculatorType()
{
    AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
        new AssemblyName("RuntimeCalculatorAssembly"),
        AssemblyBuilderAccess.RunAndCollect);

    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");

    TypeBuilder typeBuilder = moduleBuilder.DefineType(
        "RuntimeCalculator",
        TypeAttributes.Public | TypeAttributes.Class,
        typeof(object),
        new[] { typeof(ICalculator) });

    ConstructorBuilder constructor = typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);

    MethodBuilder addMethod = typeBuilder.DefineMethod(
        "Add",
        MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
        typeof(int),
        new[] { typeof(int), typeof(int) });

    ILGenerator il = addMethod.GetILGenerator();

    il.Emit(OpCodes.Ldstr, "Add invoked");
    il.Emit(OpCodes.Call, typeof(Console).GetMethod(
        nameof(Console.WriteLine),
        new[] { typeof(string) })!);

    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Ldarg_2);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Ret);

    MethodInfo interfaceMethod = typeof(ICalculator).GetMethod(nameof(ICalculator.Add))
        ?? throw new InvalidOperationException("接口方法不存在");

    typeBuilder.DefineMethodOverride(addMethod, interfaceMethod);

    return typeBuilder.CreateType()
        ?? throw new InvalidOperationException("动态类型创建失败");
}

调用:

csharp 复制代码
Type calculatorType = CreateCalculatorType();

ICalculator calculator = (ICalculator)(Activator.CreateInstance(calculatorType)
    ?? throw new InvalidOperationException("实例创建失败"));

Console.WriteLine(calculator.Add(3, 5));

输出:

text 复制代码
Add invoked
8

这类能力就是很多动态代理、Mock、AOP 框架的基础。

真实框架会在方法前后织入更多逻辑:

text 复制代码
记录日志
权限检查
事务开启
调用真实对象
事务提交
异常处理

Emit 生成的代码要缓存

Emit 的生成成本不低。

如果每次调用都重新生成动态方法,性能会很差。

错误思路:

text 复制代码
每次读取属性
  |
  v
重新创建 DynamicMethod
  |
  v
CreateDelegate
  |
  v
调用一次

正确思路:

text 复制代码
第一次遇到类型和属性
  |
  v
生成 Getter / Setter 委托
  |
  v
放入缓存
  |
  v
后续直接调用委托

简单缓存示例:

csharp 复制代码
static readonly Dictionary<string, Func<object, object?>> GetterCache = new();

static Func<object, object?> GetOrCreateGetter(Type type, string propertyName)
{
    string key = type.FullName + "." + propertyName;

    if (GetterCache.TryGetValue(key, out Func<object, object?>? getter))
    {
        return getter;
    }

    getter = CreateGetter(type, propertyName);
    GetterCache[key] = getter;

    return getter;
}

多线程环境里可以换成:

csharp 复制代码
ConcurrentDictionary<string, Func<object, object?>>

常见错误

1. 栈不平衡

IL 是栈模型。

方法返回前,栈上的数据必须和返回值匹配。

例如返回 int 的方法,ret 前栈顶必须有一个 int

错误示例:

csharp 复制代码
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ret);

这里压了两个参数,但没有做运算,返回时栈不符合预期。

轻则运行时报错,重则排查很痛苦。

2. 实例方法参数编号搞错

实例方法里:

text 复制代码
Ldarg_0 = this
Ldarg_1 = 第一个业务参数
Ldarg_2 = 第二个业务参数

静态方法里:

text 复制代码
Ldarg_0 = 第一个业务参数
Ldarg_1 = 第二个业务参数

动态实现接口时,Add(int a, int b) 是实例方法,所以加法要用:

csharp 复制代码
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Add);

3. 值类型忘记 Box / Unbox

Getter 返回 object 时,值类型要装箱:

csharp 复制代码
il.Emit(OpCodes.Box, property.PropertyType);

Setter 接收 object 时,写入值类型属性前要拆箱:

csharp 复制代码
il.Emit(OpCodes.Unbox_Any, property.PropertyType);

4. 把 Emit 当业务开发工具

Emit 很强,但不适合普通业务代码。

这些场景通常没必要用 Emit:

  • 普通增删改查
  • 简单对象映射
  • 一次性反射调用
  • 配置读取
  • 普通条件分支

能用普通 C# 写清楚,就不要上 Emit。

5. 忽略 AOT 和裁剪限制

Emit 属于运行时代码生成。

在 Native AOT、受限运行环境、强裁剪发布场景里,Emit 可能不可用或不适合。

如果目标是 AOT 友好,通常更应该考虑:

  • 源生成器
  • 手写静态代码
  • 表达式树解释执行
  • 框架提供的 AOT 模式

现代 .NET 里能保存动态程序集吗?

.NET Framework 时代可以用 RunAndSave 把动态程序集保存成文件。

现代 .NET 更常见的是内存运行:

csharp 复制代码
AssemblyBuilderAccess.Run
AssemblyBuilderAccess.RunAndCollect

如果只是为了调试 Emit,可以考虑这些方式:

  • 先用普通 C# 写等价代码,再看 IL
  • 用 SharpLab 查看 IL
  • 把 Emit 逻辑拆成小方法逐段验证
  • 给动态方法生成清晰的名称
  • 尽量用单元测试覆盖动态生成逻辑

Emit、Expression Tree、Source Generator 怎么选?

场景 更合适的选择
编译期就能知道要生成什么 Source Generator
运行时拼简单访问器或动态条件 Expression Tree
运行时生成高性能方法或类型 Emit
AOT / 裁剪友好 Source Generator
临时读取元数据 反射
框架底层动态代理 Emit 或 Castle DynamicProxy

简单判断:

text 复制代码
普通业务代码:手写
低频动态调用:反射
中等复杂动态委托:表达式树
编译期生成代码:源生成器
运行时生成类型或极致控制 IL:Emit

总结

Emit 是 .NET 里最底层、最灵活的动态代码生成能力之一。

它可以:

  • 生成动态方法
  • 生成属性访问器
  • 生成对象映射器
  • 生成完整类型
  • 生成接口实现
  • 直接发出 IL 指令

但它也有明显代价:

  • 写法复杂
  • 调试困难
  • 容易写错 IL
  • 对 AOT 不友好
  • 维护成本高

实战里的合理姿势是:

text 复制代码
把 Emit 放在框架底层和性能热点里。
生成一次,缓存起来,反复调用。
普通业务逻辑不要为了炫技使用 Emit。

理解 Emit 后,再看 ORM、Mapper、动态代理、Mock 框架的底层实现,会更容易明白它们为什么能在运行时"变出"方法和类型。

相关推荐
Caco_D5 小时前
一行代码抓遍全网 20 个热榜!Aneiang.Pa 4.0 发布 — 极简 .NET 爬虫库
爬虫·.net
咕白m6256 小时前
.NET 环境下 Word 超链接批量提取方案
c#·.net
用户91721561902117 小时前
C# 通信协议增量解析:用状态机处理半包和粘包
c#
小码编匠1 天前
C# 工控上位机必备:数据转换工具类与十个核心模块
后端·c#·.net
唐青枫3 天前
别再乱用 StartNew:C#.NET TaskFactory 任务调度实战详解
c#·.net
Artech3 天前
[MAF预定义的AIContextProvider-03]ChatHistoryMemoryProvider——赋予Agent从经验中学习的能力
ai·c#·agent·memory·maf
Scout-leaf5 天前
C#摸鱼实录——IoC与DI案例详解
c#
咕白m6255 天前
使用 C# 在 Excel 中应用多种字体样式
后端·c#