c# 没有原生编译时方法替换机制;所谓“拦截器编译替换”实为混淆了[obsolete]警告、roslyn源码分析器(需显式安装并仅作用于自有源码)及危险的运行时il注入(如preparemethod)三类技术。

直接说结论:C# 没有原生的“拦截器编译替换”机制,所谓“编译时方法替换”在标准 C# / Roslyn 编译流程中并不存在;你看到的类似说法,基本混淆了三种完全不同的技术场景:[Obsolete] 标记、源码级语法转换(Roslyn Analyzer + CodeFix)、以及危险的运行时 IL 操作(需 /unsafe+ + RuntimeHelpers.PrepareMethod)。
下面分清楚这三类,避免踩坑。
用 [Obsolete] 不是替换,只是提醒
标记一个方法为 [Obsolete("Use NewMethod instead")],只会让编译器报警告(或错误),**不会自动把调用替换成新方法**。VS 的灯泡提示可能建议你“替换为 NewMethod”,但那是 IDE 的代码修复(CodeFix),不是编译器行为。
常见错误现象:
- 加了
[Obsolete]后编译没报错 → 忘了加error: true参数 - 期望编译器“静默升级”调用 → 实际上什么都不会改,旧方法照常执行
正确做法:
- 明确写出替代方法,比如
public void NewMethod() { ... } - 手动或借助 VS 快捷键(
Ctrl+.)逐处替换调用点 - 不要依赖编译器“帮你换”,它只负责报错/警告
Roslyn 语法转换能批量改源码,但不是“编译时替换”
如果你真想在编译前自动把 OldMethod() 替成 NewMethod(),得写 Roslyn Analyzer + CodeFix,然后作为 NuGet 包引入项目。它运行在编译前的语法分析阶段,修改的是 SyntaxTree,不是 IL。
关键限制:
- 只能处理你有源码的项目(.cs 文件),对引用的第三方 DLL 无效
- 必须显式安装 analyzer 包,且需启用
EnforceCodeStyleInBuild或手动触发 - 不能改方法体逻辑,只能做 AST 层面的节点替换(如把
InvocationExpression换掉)
示例目标:把所有 obj.GetCount() 自动转成 obj.Count
你需要定义一个 SyntaxNodeAction<invocationexpressionsyntax></invocationexpressionsyntax>,匹配 GetCount 调用,再用 generator.MemberAccessExpression(...) 构造新节点 —— 这不是魔法,是硬编码的树操作。
RuntimeHelpers.PrepareMethod 是运行时 IL 注入,非编译时
网上流传的“编译时替换”教程,90% 实际是运行时 hook:RuntimeHelpers.PrepareMethod 强制 JIT 准备方法,再用 unsafe 指针覆写方法表(vtable)或 IL 内存地址。它要求:
- 必须开启
/unsafe+编译选项 - 只在 .NET Framework / .NET Core 3.1 及更早版本稳定;.NET 5+ 因 Tiered Compilation 和 ReadyToRun 机制,极易失败或崩溃
- 调试极其困难:断点失效、栈帧错乱、JIT 优化干扰
典型错误信息:
AccessViolationException(指针越界)、InvalidProgramException(IL 验证失败)、MethodAccessException(方法未准备就绪)
真实使用场景极少:仅限 AOP 框架底层(如 Fody Costura 插件)、极特殊热补丁需求。普通业务代码绝不要碰。
真正安全可控的“替换”只有两种路径
一是人肉或正则批量替换源码(适合 get_Property() → Property 这类模式化改造);二是用 Roslyn 分析器固化规则(适合团队统一治理)。所有声称“编译时自动替换方法”的方案,要么是误导,要么依赖未公开 API 或已废弃机制。
最容易被忽略的一点:即使你用 Roslyn 成功注入了替换逻辑,它也只影响下一次编译输出的 DLL;若项目引用的是已发布的 nuget 包,那些二进制里的调用点依然指向旧方法 —— 没有源码,就没有可操作的 AST。










