Dotnet 反射:如何在运行时使用更少的参数创建通用子类?

问题描述 投票:0回答:1

我需要在运行时创建一个参数少于其父类型的类型 (A:B)。 这是使用此方法的一些代码。

动机是将一些东西保留在我的项目内部。我想使用存储库,并且不想让数据库上下文对所有客户端项目公开可用。


// 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 pars
  TDbContext dbContext,
  IEditInfoSetter editInfoSetter,
  IChangePublisher<TDbContext> changePublisher)
  : IDbRepository<TEntity>
  where TDbContext : AppDbContextBase
  where TEntity : Entity
{
  //...
}

internal class App1DbRepository<TEntity>( // 1 par
  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)

// Client App with DI
public class MyClass(
  IApp1DbRepository<LogEvent> myApp1LogEvents,
  IApp2DbRepository<LogEvent> myApp2LogEvents,
  IApp2DbRepository<User> myApp2users)
{
  //...
}

是否可以创建一个执行以下操作的方法?

// This method is needed to keep types
// DbRepository<TDbContext, TEntity> and AppDbContextBase internal to their projects.

static Type CreateRepoType<TMyDbContext, TIMyDbRepository<>>()
{
  //...
  return type; // !!! : DbRepository<TMyDbContext, TEntity>, TIMyDbRepository<>
}

// To change code (1) to

  .Services
      .AddScoped(typeof(IApp1DbRepository<>), CreateRepoType<App1DbContext, IApp1DbRepository>())
      .AddScoped(typeof(IApp2DbRepository<>), CreateRepoType<App2DbContext, IApp2DbRepository>())
      .AddScoped(typeof(IApp3DbRepository<>), CreateRepoType<App3DbContext, IApp3DbRepository>())
c# .net entity-framework reflection
1个回答
0
投票

您可以使用

System.Reflection.Emit
在运行时生成类型,但不能修改当前程序集。您必须在新程序集中生成它们,这意味着基类和其他相关类必须是
public
,而不是
internal

这是概念证明:

public static class RepoCreator {
    static ModuleBuilder mb;

    static RepoCreator() {
        var assemblyName = new AssemblyName("DynamicRepos");
        var ab = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        mb = ab.DefineDynamicModule("DynamicRepos");
    }

    public static Type CreateRepoType(Type interfaceType, Type contextType) {
        // Combine the interface type name with the context type name to create a somewhat unique name for the new class
        var newTypeName = interfaceType.Name + contextType.Name + "`1";
        var tb = mb.DefineType(newTypeName);

        // then it's just some tedious code generating each part of the new class
        var tEntity = tb.DefineGenericParameters("TEntity")[0];
        tEntity.SetBaseTypeConstraint(typeof(Entity));
        var baseClass = typeof(DbRepository<,>).MakeGenericType(contextType, tEntity.AsType());
        tb.SetParent(baseClass);
        tb.AddInterfaceImplementation(interfaceType.MakeGenericType(tEntity));
        var cb = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, [
            contextType,
            typeof(IEditInfoSetter),
            typeof(IChangePublisher<>).MakeGenericType(contextType)
        ]);
        var il = cb.GetILGenerator();
        // the constructor simply loads the arguments 1 to 3, as well as 'this' (0)
        // and uses them to call the base class constructor
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldarg_2);
        il.Emit(OpCodes.Ldarg_3);
        var baseClassConstructor = baseClass.GetGenericTypeDefinition().GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
        il.Emit(OpCodes.Call, TypeBuilder.GetConstructor(baseClass, baseClassConstructor));
        il.Emit(OpCodes.Ret);
        return tb.CreateType();
    }
}

让我们测试一下:

var type = RepoCreator.CreateRepoType(typeof(IApp1DbRepository<>), typeof(App1DbContext));
Console.WriteLine(Activator.CreateInstance(type.MakeGenericType(typeof(SomeEntity)), null, null, null));
// prints: IApp1DbRepository`1App1DbContext`1[SomeEntity]
© www.soinside.com 2019 - 2024. All rights reserved.