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

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

我需要在运行时创建一个参数少于其父类型的类型

(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 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.