我需要在运行时创建一个参数少于其父类型的类型
(A
动机是将一些东西保留在我的项目内部。我想使用存储库,并且不想让数据库上下文对所有客户端项目公开可用。
// 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>())
您可以使用
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]