Interceptors 是 C# 12 的预览功能,不是运行时拦截器
它不是类似
IAuthorizationFilter或
IDispatchProxy那种在程序运行时动态介入调用的机制。C# 12 的
Interceptors是编译期重写技术:编译器识别标记了
[InterceptsLocation]的方法,在生成 IL 时,把对目标方法的调用直接替换成对拦截器方法的调用——原方法甚至不会出现在最终输出的程序集中。
这意味着:
它不依赖反射、代理或运行时织入,无性能开销 仅适用于internal或
private方法(public 方法调用可能跨程序集,无法安全重写) 必须启用预览功能:
<langversion>12.0</langversion>+
<enablepreviewfeatures>true</enablepreviewfeatures>目前仅支持源代码在同一项目中,且拦截器和被拦截方法都必须在同一个编译单元内
怎么写一个合法的拦截器方法
拦截器不是任意方法,必须严格满足签名和属性约束,否则编译失败(错误如
CS8954或
CS8955): 返回类型必须与被拦截方法完全一致(含
void、
Task、泛型等) 参数列表必须与被拦截方法**逐个位置、逐个类型匹配**(不能多参、少参、类型隐式转换也不行) 必须标注
[InterceptsLocation(...)],参数是字符串字面量,格式为
"{文件路径}:{行号}:{列号}",指向被拦截方法的声明位置
必须是 static、
internal(不能是
public或
private)
示例:
[InterceptsLocation("Program.cs:12:5")]
internal static string GetGreeting() => "Hello from interceptor!";对应被拦截的方法需写在
Program.cs第 12 行第 5 列(通常是方法名起始位置):
internal static string GetGreeting() => "Hello world"; // 这行会被完全跳过
常见失败场景:为什么拦截没生效
拦截器“静默失效”很常见,根本原因是编译器拒绝应用重写。典型原因包括:
[InterceptsLocation]路径错误:文件名大小写不符、路径是相对/绝对混淆、行号列号偏移(比如注释占行、IDE 显示行号 vs 实际行号) 被拦截方法不是
internal或
private(例如
public成员被其他项目引用) 拦截器方法和被拦截方法不在同一源文件、或分属不同
partial类但未被编译器视为同一逻辑单元 启用了增量编译但缓存未刷新,可尝试
dotnet clean后重试 使用了不支持该特性的 SDK 版本(需 .NET 8 SDK RC2+,且项目 SDK 为
Microsoft.NET.Sdk)
它不适合替代 AOP 或日志框架
Interceptors 不是
Castle.DynamicProxy、
AspectCore或
Microsoft.Extensions.DependencyInjection中的拦截扩展。它没有上下文、无法访问调用栈、不能处理异常后逻辑、也不支持条件拦截(比如只拦截特定参数值)。它的定位非常窄:用于极底层、确定性替换,比如: 在测试中静态替换某个配置读取方法,避免启动完整 DI 容器 在发布构建中将调试日志调用直接替换为空实现 为特定平台 API 提供编译期 fallback 实现(如 Windows-only 方法在 Linux 下替换为 stub)
一旦你想到“我需要在方法执行前后做点什么”,那它就不是合适工具——这时候该用
Source Generators、
DiagnosticAnalyzer,或者老老实实写装饰器。
