如何在程序集级别全局替换类实现?支持鸭子类型与单点配置
问题
希望在程序集级别替换通用类System.Collections.Generic.List<T>,让项目及引用的其他程序集中所有对该类的调用(比如var values = new List<int> { 1, 2, 3, 4, 5 })都自动使用自定义的List<T>实现。要求:
- 无需修改多处代码引用,仅通过单点配置实现
- 支持鸭子类型替换:自定义类无需继承原类,但需暴露相同的方法签名
- 全局层面的解决方案,可通过反射或配置实现单点控制
- 曾实现过动态替换方法,可参考动态向方法追加代码的思路
解决方案
方法1:类型转发 + 程序集绑定重定向
此方法通过CLR的类型转发机制,将对原类型的引用重定向到自定义类型,适合替换系统或第三方程序集中的类型。
步骤:
- 创建自定义
List<T>类,确保方法签名、属性与原类完全一致(满足鸭子类型要求),命名空间保持为System.Collections.Generic。 - 在自定义类所在的程序集中添加类型转发属性:
[assembly: TypeForwardedTo(typeof(System.Collections.Generic.List<>))]
- 在项目配置文件(
app.config/web.config/runtimeconfig.json)中添加程序集绑定重定向,让CLR优先加载你的自定义程序集:
<runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Collections" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="1.0.0.0" /> <!-- 自定义程序集版本 --> <codeBase version="1.0.0.0" href="YourCustomListAssembly.dll" /> </dependentAssembly> </assemblyBinding> </runtime>
局限性:
- 自定义类必须与原类同名同命名空间
- 系统程序集带有强签名,需额外处理签名替换,操作复杂度较高
方法2:编译时IL重写(Fody插件)
使用Fody框架编写自定义IL重写插件,在编译阶段自动替换所有对原List<T>的引用为自定义类型,无需修改业务代码。
步骤:
- 安装Fody及插件模板,创建自定义Fody插件项目。
- 编写插件核心逻辑,遍历程序集IL指令,替换类型引用:
public void ProcessModule(ModuleDefinition module) { var originalListType = module.TypeSystem.GetType("System.Collections.Generic.List`1"); var customListType = module.TypeSystem.GetType("YourNamespace.CustomList`1"); foreach (var type in module.Types) { foreach (var method in type.Methods.Where(m => m.Body != null)) { foreach (var instruction in method.Body.Instructions) { if (instruction.Operand is TypeReference tr && tr.FullName == originalListType.FullName) { instruction.Operand = customListType; } } } } }
- 在目标项目中配置Fody插件,编译时自动完成全局替换。
优势:
- 完全无侵入,无需修改业务代码
- 支持鸭子类型替换,自定义类无需继承原类
- 一次性配置,所有引用自动生效
方法3:运行时自定义类型加载器
通过实现AssemblyLoadContext拦截类型加载请求,将原List<T>的加载重定向到自定义类型,适合运行时动态替换场景。
步骤:
- 继承
AssemblyLoadContext,重写类型加载逻辑:
public class CustomTypeLoadContext : AssemblyLoadContext { protected override Type LoadType(TypeName typeName) { // 匹配原List<T>的全名 if (typeName.FullName == "System.Collections.Generic.List`1") { return typeof(YourNamespace.CustomList<>); } return base.LoadType(typeName); } }
- 程序启动时使用自定义加载器加载所有相关程序集:
var loadContext = new CustomTypeLoadContext(); var targetAssembly = loadContext.LoadFromAssemblyPath("YourProject.dll"); // 执行程序入口 targetAssembly.EntryPoint.Invoke(null, new object[] { Environment.GetCommandLineArgs() });
优势:
- 全局生效,覆盖所有类型加载场景
- 支持鸭子类型替换,无需自定义类与原类同名
- 单点配置,无需修改编译产物
局限性:
- 需要通过自定义加载器加载所有目标程序集,已预加载的系统程序集可能无法拦截
方法4:运行时IL注入(基于动态方法替换)
结合你之前实现的动态方法修改思路,在程序启动时通过反射遍历所有方法,替换其中new List<T>()及方法调用的IL指令为自定义类型的对应逻辑。
核心思路:
- 遍历所有已加载程序集的类型和方法
- 解析方法IL指令,找到所有创建
List<T>实例、调用List<T>方法的指令 - 将这些指令替换为自定义类型的实例创建、方法调用指令
注意:
此方式复杂度极高,需处理泛型重载、方法签名匹配等细节,适合深度定制场景,不推荐作为通用方案。
内容的提问来源于stack exchange,提问作者johnny 5
相关产品推荐
相关产品推荐

