1

我什至不确定这是否可能。在一个方法中,我正在创建一个动态程序集,定义一个类型,并为该类型的构造函数发出 IL。此方法将 aIEnumerable<Action>作为参数,我希望能够在我生成的类中使用该引用。

我已经编写了一些数据库迁移助手来使用 FluentMigrator 或 MigratorDotNet,并且我正在尝试实施单元测试来验证正确的功能。使用 FluentMigrator,我可以实例化一个跑步者并将其传递给 Migration 类的实例。然而,对于 MigratorDotNet,它需要我向它传递一个程序集,它会扫描 Migration 类以实例化和运行 - 因此是动态生成。

这是我动态子类化的基类:

    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 加载指令中对存在于动态程序集上下文之外的这个本地对象进行引用吗?表达式可以帮助吗?也许尝试在构造函数中传递它是错误的方法 - 也许是覆盖 up 和 down 实现(我可以像一个 Action 的函数句柄并发出对它的调用,而不是传递对它的引用吗? IEnumerable?)。

我对汇编和 IL 非常熟悉,在回顾了操作之后,我开始认为我可能无法做我想做的事情。

那么,这是否可能,如果可以,有人可以将我推向正确的方向吗?

对于好奇的人,完整的代码在这里

4

1 回答 1

0

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);
于 2017-05-05T07:56:30.040 回答