You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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参数,同时实现对应的公开仓储接口。

实现思路

  1. 类型约束匹配:生成的动态类型需满足两个核心条件:
    • 继承DbRepository<TMyDbContext, TEntity>,其中TMyDbContext为传入的具体上下文类型,TEntity为动态泛型参数
    • 实现TIMyDbRepository<TEntity>公开接口
  2. 构造函数继承:动态类型需要显式调用基类的构造函数,确保依赖注入可以正常传递TMyDbContext、IEditInfoSetter和IChangePublisher<TMyDbContext>
  3. 内部类型访问权限:因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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 02:05:56