.NET反射:如何在运行时创建泛型子类并减少参数?
问题
我需要在运行时创建一个泛型参数比其父类更少的类型(比如A<X,Y> : B<X,Y,Z>),核心动机是将部分类型限定在项目内部:想用仓储模式,但不想把DbContext暴露给所有客户端项目。
现有代码结构如下:
// DOMAIN LAYER public interface IDbRepository<TEntity> where TEntity : Entity { int Save(TEntity entity); // ... } // INFRASTRUCTURE LAYER public interface IApp1DbRepository<TEntity> : IDbRepository<TEntity>; public interface IApp2DbRepository<TEntity> : IDbRepository<TEntity>; public interface IApp3DbRepository<TEntity> : IDbRepository<TEntity>; ... internal abstract class DbRepository<TDbContext, TEntity>( // 2个泛型参数 TDbContext dbContext, IEditInfoSetter editInfoSetter, IChangePublisher<TDbContext> changePublisher) : IDbRepository<TEntity> where TDbContext : AppDbContextBase where TEntity : Entity { //... } internal class App1DbRepository<TEntity>( // 1个泛型参数 App1DbContext dbContext, IEditInfoSetter editInfoSetter, IChangePublisher<App1DbContext> changePublisher) : DbRepository<App1DbContext, TEntity>(dbContext, editInfoSetter, changePublisher), IApp1DbRepository<TEntity> where TEntity : Entity { } ... .Services .AddScoped(typeof(IApp1DbRepository<>), typeof(App1DbRepository<>)) .AddScoped(typeof(IApp2DbRepository<>), typeof(App2DbRepository<>)) .AddScoped(typeof(IApp3DbRepository<>), typeof(App3DbRepository<>)) // (1) 这里是要替换的DI注册代码 // Client App with DI public class MyClass( IApp1DbRepository<LogEvent> myApp1LogEvents, IApp2DbRepository<LogEvent> myApp2LogEvents, IApp2DbRepository<User> myApp2users) { //... }
我想实现一个动态生成仓储类型的方法,替代手动编写的AppXDbRepository类,同时保持DbRepository和AppDbContextBase的internal访问级别,具体期望如下:
// 这个方法用来封装内部类型 // DbRepository<TDbContext, TEntity> 和 AppDbContextBase 仅在项目内部可见 static Type CreateRepoType<TMyDbContext, TIMyDbRepository<>>() { //... return type; // 返回的类型需要继承 DbRepository<TMyDbContext, TEntity> 并实现 TIMyDbRepository<> } // 将代码(1)替换为: .Services .AddScoped(typeof(IApp1DbRepository<>), CreateRepoType<App1DbContext, IApp1DbRepository>()) .AddScoped(typeof(IApp2DbRepository<>), CreateRepoType<App2DbContext, IApp2DbRepository>()) .AddScoped(typeof(IApp3DbRepository<>), CreateRepoType<App3DbContext, IApp3DbRepository>())
这样可行吗?
解决方案
完全可以通过动态类型生成实现这个需求,核心是利用.NET的System.Reflection.Emit或第三方动态代理库,在运行时创建泛型类型,自动绑定固定的TDbContext参数,同时实现对应的公开仓储接口。
实现思路
- 类型约束匹配:生成的动态类型需满足两个核心条件:
- 继承
DbRepository<TMyDbContext, TEntity>,其中TMyDbContext为传入的具体上下文类型,TEntity为动态泛型参数 - 实现
TIMyDbRepository<TEntity>公开接口
- 继承
- 构造函数继承:动态类型需要显式调用基类的构造函数,确保依赖注入可以正常传递
TMyDbContext、IEditInfoSetter和IChangePublisher<TMyDbContext> - 内部类型访问权限:因
DbRepository是internal,需通过InternalsVisibleTo属性允许动态程序集访问该程序集的内部成员
基于Reflection.Emit的简化实现示例
using System.Reflection; using System.Reflection.Emit; static Type CreateRepoType<TDbContext, TRepoInterface>() where TDbContext : AppDbContextBase where TRepoInterface : class { // 获取泛型接口的定义(如IApp1DbRepository<>) var repoInterfaceDef = typeof(TRepoInterface).IsGenericTypeDefinition ? typeof(TRepoInterface) : typeof(TRepoInterface).GetGenericTypeDefinition(); // 获取基类定义并绑定具体泛型参数:DbRepository<TDbContext, TEntity> var baseRepoDef = typeof(DbRepository<,>); var closedBaseType = baseRepoDef.MakeGenericType(typeof(TDbContext), repoInterfaceDef.GetGenericArguments()[0]); // 创建动态程序集(需配置InternalsVisibleTo允许访问内部类型) var assemblyName = new AssemblyName($"DynamicRepos_{Guid.NewGuid()}"); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name); // 定义动态泛型类型:DynamicRepo<TEntity> var typeBuilder = moduleBuilder.DefineType( $"Dynamic{typeof(TDbContext).Name}Repo`1", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed, closedBaseType); // 绑定实现公开接口 var closedInterfaceType = repoInterfaceDef.MakeGenericType(repoInterfaceDef.GetGenericArguments()[0]); typeBuilder.AddInterfaceImplementation(closedInterfaceType); // 复制基类构造函数并调用基类逻辑 var baseCtor = closedBaseType.GetConstructors(BindingFlags.Public | BindingFlags.Instance)[0]; var ctorParams = baseCtor.GetParameters(); var ctorBuilder = typeBuilder.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, ctorParams.Select(p => p.ParameterType).ToArray()); var il = ctorBuilder.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); for (int i = 1; i <= ctorParams.Length; i++) { il.Emit(OpCodes.Ldarg, i); } il.Emit(OpCodes.Call, baseCtor); il.Emit(OpCodes.Ret); // 生成并返回动态类型 return typeBuilder.CreateType()!; }
关键注意事项
- 内部访问权限配置:在
DbRepository所在程序集的AssemblyInfo.cs中添加以下代码,允许动态程序集访问内部成员:[assembly: InternalsVisibleTo("DynamicRepos_*")] - DI注册兼容性:生成的动态类型是泛型类型定义,可直接传入
AddScoped方法,与手动编写的类行为完全一致 - 性能优化:动态类型仅在DI注册时生成一次,后续实例化性能与普通类无差异
替代方案:使用动态代理库
若不想手动编写Reflection.Emit代码,可使用Castle.DynamicProxy等第三方库,简化动态类型创建流程,无需手动处理IL指令,仅需配置继承和接口实现规则即可。
内容的提问来源于stack exchange,提问作者Neomaster
相关产品推荐
相关产品推荐

