.NET 高级开发 | C# 中的动态代码:反射、EMIT、表达式树、Roslyn、Source Generators
MIT 是一种使用 C# 编排生成 IL 代码的技术,IL 是 .NET 平台的中间语言,由于 IL 的高性能的特点,很多框架都使用 EMIT 技术动态生成代码,最广泛的使用是编写 AOP 框架。在本节中,笔者将会介绍 AOP 的实现原理,以及使用 EMIT 编写一个简单的 AOP 程序。
创建控制台项目,引入 CZGL.AOP 包,示例代码请参考 Demo.CZGLAOP 项目。
有以下接口和类型:
public interface ITest
{
void MyMethod();
}
public class Test : ITest
{
public virtual string A { get; set; }
public Test()
{
Console.WriteLine("构造函数没问题");
}
public virtual void MyMethod()
{
Console.WriteLine("运行中");
}
}
我们希望,在执行 MyMethod 方法时,能够在执行前后打印出日志,这时可以先编写一个特性类,继承 ActionAttribute ,实现 Before 和 After 接口。
public class LogAttribute : ActionAttribute
{
public override void Before(AspectContext context)
{
Console.WriteLine("--执行前--");
}
public override object After(AspectContext context)
{
Console.WriteLine("--执行后--");
if (context.IsMethod)
return context.MethodResult;
else if (context.IsProperty)
return context.PropertyValue;
return null;
}
}
然后改造 Test 类型。
[Interceptor]
public class Test : ITest
{
[Log]
public virtual string A { get; set; }
public Test()
{
Console.WriteLine("构造函数");
}
[Log]
public virtual void MyMethod()
{
Console.WriteLine("运行中");
}
}
然后创建 AOP 类型:
ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>();
test1.MyMethod();
Test test2 = AopInterceptor.CreateProxyOfClass<Test>();
test2.MyMethod();
运行项目,会输出:
构造函数
--执行前--
运行中
--执行后--
构造函数
--执行前--
运行中
AOP 实现原理
AOP(Aspect-oriented Programming) 即面向切片编程,在 C# 中有动态 AOP 和静态 AOP 两种,如果是在程序启动后生成的,为动态 AOP,这类框架有 Castle 、AspectCore 等,它们都使用了 EMIT 技术,在代码编译时即生成的,为静态 AOP,这类框架有 Fody 等。
实现 AOP 的前提
请看如下所示的代码,当调用 Voice() 方法时,请思考控制台会打印什么内容。
public class Program
{
static void Main()
{
Animal c = new Cat();
Console.WriteLine(c.Voice());
}
public abstract class Animal
{
public string Voice() => "null";
}
public class Cat: Animal
{
public new string Voice() => "喵";
}
}
如果你有运行代码,会发现打印结果是 null,虽然 c 是 Cat 类型,但是这里我们使用的是 Animal 类型,CLR 首先判断 Voice 方法是否为抽象方法或虚方法,如果不是则直接调用,不会往子类中查找。
我们使用工具查看Animal 中 Voice 方法的 IL 代码:
// Methods
.method public hidebysig
instance string Voice () cil managed
那么,同样的代码,在 java 中,又会发生什么呢?
public class Main {
public static void main(String[] args) {
Animal animal = new Cat();
System.out.println(animal.Voice());
}
}
class Animal{
public String Voice(){
return "null";
}
}
class Cat extends Animal{
public String Voice(){
return "喵";
}
}
运行这段代码后会发现,打印出来的是 喵。因为 java 中的方法默认是虚方法,而 C# 中的 方法需要加上关键字 virtual 才是虚方法。
为了能够在使用父类方法时,执行的是子类的代码,我们需要将代码改成:
public abstract class Animal
{
public virtual string Voice() => "null";
}
public class Cat : Animal
{
public override string Voice() => "喵";
}
// Methods
.method public hidebysig newslot virtual
instance string Voice () cil managed
那么,虚方法跟实现 AOP 有啥关系呢?其实,使用 EMIT 技术编写 AOP 框架的思路很简单,那就是继承,比如我们要给 A 类型的 A 方法实现 AOP,那么 A 方法就必须得是抽象方法或虚方法,然后我们通过 EMIT 技术生成一个类型 B 继承 A,然后创建 A 类型时实际上创建的是 B 类型。此时,调用 A 中的 A 方法,CLR 会执行 B 中的 A 方法。
A a = new B();
在使用 EMIT 技术实现 AOP 之前,我们可以通过容器依赖注入来领会 AOP 是如何通过继承实现的。
有个 UserService 服务,实现了登录过程。
public class UserService
{
public virtual bool Login(string name, string passeword)
{
return true;
}
}
然后我们使用一个新的类型重写 Login 方法,在执行方法之前和之后打印日志。
public class UserServiceAop : UserService
{
public override bool Login(string name, string passeword)
{
Console.WriteLine($"用户开始登录:{name}");
var result = base.Login(name, passeword);
Console.WriteLine($"用户 {name} 登录结果 {result}");
return result;
}
}
然后通过注入服务实现 AOP:
IServiceCollection ioc = new ServiceCollection();
ioc.AddScoped<UserService, UserServiceAop>();
var services = ioc.BuildServiceProvider();
var userService = services.GetRequiredService<UserService>();
userService.Login("工良", "123456");
如果需要 AOP 的是接口,那就更加简单,我们只需要生成一个继承接口的类型即可,完全不需要继承父类,而父类也不需要标记为虚方法或抽象方法。
public interface IUserService
{
bool Login(string name, string passeword);
}
public class UserService : IUserService
{
public bool Login(string name, string passeword)
{
return true;
}
}
public class UserServiceAop : IUserService
{
private readonly UserService _service;
public UserServiceAop()
{
_service = new UserService();
}
public bool Login(string name, string passeword)
{
Console.WriteLine($"用户开始登录:{name}");
var result = _service.Login(name, passeword);
Console.WriteLine($"用户 {name} 登录结果 {result}");
return result;
}
}
IServiceCollection ioc = new ServiceCollection();
ioc.AddScoped<IUserService, UserServiceAop>();
var services = ioc.BuildServiceProvider();
var userService = services.GetRequiredService<IUserService>();
userService.Login("工良", "123456");
实际上,接口方法本身就是抽象方法,所以因此无需处理接口即可直接使用 AOP 生成代理类型。
// Methods
.method public hidebysig newslot abstract virtual
instance bool Login (
string name,
string passeword
) cil managed
{
} // end of method IUserService::Login
通过这个例子你应该可以领会到使用 EMIT 技术实现 AOP ,最基础最本质的是实现一个新的类型基础父类或接口。由于 AOP 的类型是动态生成的,我们在开发是无法创建,所以 AOP 一般需要结合 IOC 使用,我们可以拦截容器中的 <IUserService, UserService>,替换成 <IUserService, UserServiceAop>。或者提供一个工厂服务,用于获取 AOP 后的对象。
EMIT 实现 AOP
本节代码可以在 Demo8.Console、Demo8.FxConsole 中查看。
ILSpy 是一个反编译工具,能够帮助我们查看代码生成的 IL,这样一来,即使我们对 IL 不熟悉,也可以通过编译好的 IL 代码中抄过来。
在 Visual Studio 中 安装扩展 ILSpy ,安装或手动下载(GitHub - icsharpcode/ILSpy: .NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform! · GitHub)。

创建一个项目,然后创建接口和类型。
public interface IUserService
{
bool Login(string name, string passeword);
}
public class UserService : IUserService
{
public bool Login(string name, string passeword)
{
return true;
}
}
public class UserServiceAop : IUserService
{
private readonly UserService _service;
public UserServiceAop()
{
_service = new UserService();
}
public bool Login(string name, string passeword)
{
Console.WriteLine($"用户开始登录:{name}");
var result = _service.Login(name, passeword);
Console.WriteLine($"用户 {name} 登录结果 {result}");
return result;
}
}
然后编译项目生成 dll 文件,将 Demo8.Console.dll 拖动放到 ILSpy 中,你可以查看到 UserServiceAop 的全部 IL 代码,我们只需要抄即可!

接下来,我们开始使用 EMIT 技术,动态生成一个 UserServiceAop 类型。
首先是动态构建程序集并创建一个新的类型。
public static Assembly Build()
{
// 构建运行时程序集
AssemblyName assemblyName = new AssemblyName("AopTmp");
assemblyName.SetPublicKeyToken(new Guid().ToByteArray());
AssemblyBuilder assBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect);
// 构建模块
ModuleBuilder moduleBuilder = assBuilder.DefineDynamicModule(assemblyName.Name);
/// 构建类型,命名空间+类名
TypeBuilder typeBuilder = moduleBuilder.DefineType("Aop.UserServiceAop",
TypeAttributes.Public, parent: null, interfaces: typeof(UserService).GetInterfaces());
// 构建字段
// field private initonly class Program/UserService _service
var fieldBuilder = typeBuilder.DefineField("_service", typeof(UserService), FieldAttributes.Private | FieldAttributes.InitOnly);
BuildCtor(typeBuilder, fieldBuilder);
BuildMethod(typeBuilder, fieldBuilder);
var type = typeBuilder.CreateType();
return assBuilder;
}
创建类型后,首先给类型添加构造函数。
private static void BuildCtor(TypeBuilder typeBuilder, FieldBuilder fieldBuilder)
{
// 构造函数更多推荐
所有评论(0)