必须在.csproj中同时配置true、preview和interceptors-experimental,且目标sdk需为.net 8+,路径与行列号须精确匹配调用位置。

不能直接“使用”拦截器——它不是运行时可调用的组件,而是编译期重写调用的实验性机制,必须配合源码位置精确标注和项目配置才能生效。
怎么启用拦截器功能(.csproj 配置)
拦截器是实验性特性,不启用就完全不可见,连编译错误都不会报。
- 在
.csproj文件中必须同时启用两项:<enablepreviewfeatures>true</enablepreviewfeatures>和<langversion>preview</langversion> - 还需显式添加
<features>interceptors-experimental</features>—— 仅靠 preview 语言版本不够,漏掉这行会导致[InterceptsLocation]被静默忽略 - 目标 SDK 必须为 .NET 8+(C# 12),Visual Studio 2022 v17.7 预览版 3 或更高版本;旧版 IDE 可能无法识别该特性
[InterceptsLocation] 的参数到底怎么填
这个特性的三个参数不是“大概位置”,而是编译器用来做 AST 精确匹配的坐标,填错就等于没写。
- 第一参数是
FilePath:必须是相对或绝对路径,且与编译器看到的路径完全一致(比如用dotnet build时工作目录下的路径)。常见坑:"Program.cs"在多项目中可能被解析为"src/MyApp/Program.cs",路径不匹配即失效 - 第二、三参数是
Line和Column:指**调用语句起始位置**,不是方法定义行。例如Log("hello");在第 15 行第 3 列,则填(15, 3);若写成(15, 1)(行首空格),可能因缩进差异导致匹配失败 - 不支持通配符或模糊匹配:不能写
Line = 10-12,也不能用方法名自动查找——nameof(Log)在部分重载场景下会歧义,官方文档明确建议优先用文件+行列
拦截方法签名必须严格兼容原调用
编译器不会帮你做隐式转换,类型不一致会导致编译失败,且错误提示非常不直观(常报“找不到匹配的拦截器”而非类型错误)。
- 返回类型必须相同:若原调用是
void Log(string),拦截方法也必须是void LogInterceptor(string),不能是Task或bool - 参数顺序、数量、类型必须一一对应:值类型不能自动装箱,
string?和string被视为不同签名;ref/out 参数必须显式标注 - 静态方法限定:拦截方法必须是
public static,且不能是泛型方法(即使闭合泛型也不行),也不能在泛型类内部定义 - 不能调用原方法:拦截方法体里**不能手动写
Log(message)**——编译器会在生成代码时自动插入原逻辑(类似“环绕通知”),你只负责前置/后置逻辑;手动调用会导致重复执行
为什么本地能跑,CI 构建却失效
拦截器对构建环境极其敏感,CI 失败往往不是代码问题,而是路径或上下文偏差。
- 路径大小写敏感:Linux CI 中
"program.cs"≠"Program.cs",而 Windows 不敏感,导致本地 OK、CI 报“未找到匹配位置” - 源码生成器干扰:若项目同时用了其他源生成器(如
System.Text.Json源生成),它们可能提前修改了语法树,使原始调用位置偏移,导致行列号失准 - 增量编译残留:VS 或 dotnet CLI 的增量编译可能缓存旧 AST,改完
[InterceptsLocation]后需执行dotnet clean && dotnet build彻底重建 - 调试时看不到跳转:VS 调试器仍显示停在原调用行,但实际执行的是拦截方法——这不是 bug,是编译期替换的必然表现;检查反编译 IL 或日志输出才能确认是否生效
最易被忽略的一点:拦截器目前不支持跨程序集调用拦截。所有拦截逻辑、被拦截方法、[InterceptsLocation] 标注必须在同一个编译单元(即同一 .csproj 内),引用的 NuGet 包或外部 DLL 中的方法无法被拦截——这点和传统 AOP 完全不同,别指望拿它去 hook System.Console.WriteLine。











