Castle.DynamicProxy 是运行时轻量AOP事实标准,仅支持拦截virtual方法或接口实现;需注册IInterceptor服务并面向接口编程,避免吞异常、注意DI生命周期与性能影响。

用 Castle.DynamicProxy 实现运行时代理(最常用)
绝大多数 C# 项目需要轻量、无侵入的 AOP,Castle.DynamicProxy 是事实标准。它不依赖编译器插件或 IL 织入,只在运行时生成代理类,兼容 .NET Standard 2.0+ 和所有主流 DI 容器。
关键限制:只能拦截 虚方法(virtual)或接口实现,非虚实例方法无法被代理 —— 这不是 bug,是动态代理的本质限制。
- 必须让目标类的方法声明为
virtual,或让类实现接口并面向接口编程 - 注册服务时要用
AddTransient<iuserservice userservice>()</iuserservice>,而不是直接注册具体类型 - 拦截器需继承
IInterceptor,在Intercept方法里调用invocation.Proceed()控制执行流 - 不要在拦截器里捕获所有异常后吞掉 ——
invocation.ReturnValue和invocation.Exception都要显式处理,否则上层看不到真实错误
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"→ {invocation.Method.Name}");
try
{
invocation.Proceed(); // 必须调用,否则方法不执行
Console.WriteLine($"← {invocation.Method.Name} OK");
}
catch (Exception ex)
{
Console.WriteLine($"× {invocation.Method.Name} failed: {ex.Message}");
throw; // 不建议静默吞异常
}
}
}用 Microsoft.Extensions.DependencyInjection + 拦截器自动注册
.NET 6+ 的 DI 容器本身不支持 AOP,但可以结合 Castle.DynamicProxy 封装出“开箱即用”的拦截注册方式。重点在于避免手动 new 拦截器实例 —— 否则无法享受 DI 生命周期管理(比如注入 ILogger<t></t>)。
- 拦截器类必须注册为服务,例如
services.AddSingleton<iinterceptor logginginterceptor>()</iinterceptor> - 使用第三方扩展如
Scrutor或手写AddTransientWithInterceptors扩展方法,把代理创建逻辑封装掉 - 如果拦截器依赖了 scoped 服务(如
DbContext),必须确保代理对象也是 scoped,否则会引发InvalidOperationException:Cannot resolve scoped service from root provider - 别在构造函数里做耗时操作 —— 代理类的构造和拦截器的构造都发生在解析时刻,会影响整个请求链路延迟
编译期织入:Fody.Costura 或 PostSharp 要谨慎选
这类方案在 MSBuild 编译阶段修改 IL,能拦截任意方法(包括 private、sealed、static),但代价明显:构建变慢、调试符号错位、IDE 支持弱、升级 SDK 时常断裂。
-
PostSharp功能强但商业授权贵,免费版限制多(如仅支持 1 个切面),且 .NET 6+ 对其兼容性仍不稳定 -
Fody开源免费,但插件生态参差不齐;Fody.PropertyChanged稳定,Fody.MethodDecorator则容易因方法签名变更导致织入失败 - 一旦启用,所有开发者机器和 CI 环境都必须安装对应 Fody 插件,否则编译报错:
Error Fody: Could not find a weaver named 'MethodDecorator' - 发布时若用
Trimmed发布模式,部分织入代码可能被裁剪,运行时报MissingMethodException
不用第三方库:靠 Attribute + 手动装饰(适合简单场景)
如果只是记录日志、校验参数、重试几次,又不想引入新依赖,可以用自定义 Attribute 配合模板方法或委托包装,属于“伪 AOP”,但足够清晰可控。
- 定义
[LogExecution]特性,但不靠反射自动触发 —— 而是在调用处显式包装:LogExecution.Wrap(() => service.DoWork()) - 避免用
StackFrame获取调用方信息,性能差且 Release 模式下常为空;改用 CallerMemberName 参数 - 这种写法本质是模板方法,不是真正的横切,但胜在无外部依赖、可单元测试、堆栈干净
- 不适合高频调用路径(如每毫秒调一次的实时计算),因为每次都会新建委托和闭包对象,GC 压力会上升
真正难的不是选哪种技术,而是想清楚:这个“切面”是否真该跨多个模块复用?还是只是某几个方法的共性逻辑,直接抽成 helper 方法更直白?很多所谓 AOP 需求,最后发现加一行 logger.LogInformation 就够了。



















