我什至不确定是否可以。此方法将IEnumerable<Action>
作为参数,我希望能够在我生成的类中使用该参考。
我已经编写了一些数据库迁移帮助者,以与FluentMigrator或MigratorDotnet一起工作,并且我正在尝试实现单元测试以验证正确的功能。使用FluentMigrator,我能够实例化跑步者并通过迁移类别的实例。但是,使用MigratorDotnet,它要求我通过它通过一个组件来扫描迁移类以实例化和运行 - 因此动态生成。
这是我动态分类的基类:
public class ActionMigration : Migration
{
private readonly IEnumerable<Action<Migration>> _up;
private readonly IEnumerable<Action<Migration>> _down;
public ActionMigration(Action<Migration> migration) : this(migration, migration) { }
public ActionMigration(Action<Migration> up, Action<Migration> down) : this(new[] { up }, new[] { down }) { }
public ActionMigration(IEnumerable<Action<Migration>> actions) : this(actions, actions) { }
public ActionMigration(IEnumerable<Action<Migration>> up, IEnumerable<Action<Migration>> down) { _up = up; _down = down; }
public override void Down() => _down?.ForEach(m => m(this));
public override void Up() => _up?.ForEach(m => m(this));
}
这是我的代码生成动态实现:
private Assembly BuildMigrationAssembly(IEnumerable<Action<Migration>> actions)
{
var assemblyName = $"mdn_test_{Guid.NewGuid()}";
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.RunAndSave);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyBuilder.GetName().Name, assemblyName + ".dll");
BuildMigrationClass(moduleBuilder, 1, actions);
return assemblyBuilder;
}
private void BuildMigrationClass(ModuleBuilder moduleBuilder, long version, IEnumerable<Action<Migration>> actions)
{
var baseType = typeof(ActionMigration);
var typeBuilder = moduleBuilder.DefineType($"Migration{version}",
TypeAttributes.Public | TypeAttributes.Class |
TypeAttributes.AutoClass | TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout,
baseType);
var migAttrType = typeof(MigrationAttribute);
var migAttrCtor = migAttrType.GetConstructor(new[] { typeof(long) });
typeBuilder.SetCustomAttribute(migAttrCtor, BitConverter.GetBytes(version));
var baseCtor = baseType.GetConstructor(new[] { typeof(IEnumerable<Action<Migration>>) });
var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);
var ilg = ctor.GetILGenerator();
ilg.Emit(OpCodes.Ldarg_0);
// how can I pass the local 'actions' object to the base constructor here?
ilg.Emit(OpCodes.Call, baseCtor);
ilg.Emit(OpCodes.Nop);
ilg.Emit(OpCodes.Nop);
ilg.Emit(OpCodes.Ret);
}
我打开了一个项目,并创建了一些样本子类以检查:
namespace MdnTest
{
[Migration(1)]
public class Migration1 : EasyMigrator.Tests.Integration.Migrators.MigratorDotNet.ActionMigration
{
public Migration1() : base(new List<Action<Migration>>()) { }
}
}
或:
namespace MdnTest
{
[Migration(1)]
public class Migration1 : EasyMigrator.Tests.Integration.Migrators.MigratorDotNet.ActionMigration
{
static private readonly IEnumerable<Action<Migration>> _actions;
public Migration1() : base(_actions) { }
}
}
这是它们生成的IL:
.class public auto ansi beforefieldinit
MdnTest.Migration1
extends [EasyMigrator.Tests]EasyMigrator.Tests.Integration.Migrators.MigratorDotNet/ActionMigration
{
.custom instance void [Migrator.Framework]Migrator.Framework.MigrationAttribute::.ctor(int64)
= (01 00 01 00 00 00 00 00 00 00 00 00 ) // ............
// int64(1) // 0x0000000000000001
.method public hidebysig specialname rtspecialname instance void
.ctor() cil managed
{
.maxstack 8
// [14 31 - 14 66]
IL_0000: ldarg.0 // this
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>>::.ctor()
IL_0006: call instance void [EasyMigrator.Tests]EasyMigrator.Tests.Integration.Migrators.MigratorDotNet/ActionMigration::.ctor(class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>>)
IL_000b: nop
// [14 67 - 14 68]
IL_000c: nop
// [14 69 - 14 70]
IL_000d: ret
} // end of method Migration1::.ctor
} // end of class MdnTest.Migration1
或:
.class public auto ansi beforefieldinit
MdnTest.Migration1
extends [EasyMigrator.Tests]EasyMigrator.Tests.Integration.Migrators.MigratorDotNet/ActionMigration
{
.custom instance void [Migrator.Framework]Migrator.Framework.MigrationAttribute::.ctor(int64)
= (01 00 01 00 00 00 00 00 00 00 00 00 ) // ............
// int64(1) // 0x0000000000000001
.field private static initonly class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>> _actions
.method public hidebysig specialname rtspecialname instance void
.ctor() cil managed
{
.maxstack 8
// [15 31 - 15 45]
IL_0000: ldarg.0 // this
IL_0001: ldsfld class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>> MdnTest.Migration1::_actions
IL_0006: call instance void [EasyMigrator.Tests]EasyMigrator.Tests.Integration.Migrators.MigratorDotNet/ActionMigration::.ctor(class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Action`1<class [Migrator.Framework]Migrator.Framework.Migration>>)
IL_000b: nop
// [15 46 - 15 47]
IL_000c: nop
// [15 48 - 15 49]
IL_000d: ret
} // end of method Migration1::.ctor
} // end of class MdnTest.Migration1
我不确定如何使其适应我要实现的目标。.我可以在IL负载指令中对此动态组件之外的本地对象进行引用?表达可以帮助吗?也许试图将其传递到构造函数中是错误的方法 - 也许可以覆盖向上和向下的实现(我可以像操作的功能一样掌握并发出呼叫,而不是通过引用ienumerable?)。
我对汇编和IL非常熟悉,在审查了我开始认为我可能无法做的事情的操作之后。
那么,这甚至可能,如果是这样,有人可以朝正确的方向推动我吗?
很好奇,完整的代码就在这里。
您可以通过用类型IEnumerable<Action<Migration>>
的参数定义动态类别的构造函数来引用对在动态组件外部存在的对象:
var ctor = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
new[] { typeof(IEnumerable<Action<Migration>>) });
然后使用这些参数将其在基类构造函数中进行路径:
var ilg = ctor.GetILGenerator();
ilg.Emit(OpCodes.Ldarg_0); // load 'this' onto stack
ilg.Emit(OpCodes.Ldarg_1); // load constructor argument onto the stack
ilg.Emit(OpCodes.Call, baseCtor); // call base constructor
ilg.Emit(OpCodes.Ret);
之后,您将能够使用Activator
创建动态类的实例:
var type = typeBuilder.CreateType();
var args = new object[] { new List<Action<Migration>>() };
var instance = Activator.CreateInstance(type, args);