2017-03-08 12 views
1

これが可能かどうかわからない。私は動的アセンブリを作成し、型を定義し、そのタイプ。このメソッドはパラメータとしてIEnumerable<Action>を受け取り、私が生成しているクラスの内部でその参照を使用できるようにしたいと考えています。ILで動的に生成されたクラスでローカルオブジェクトを使用する方法Emit

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ロード命令の動的アセンブリの文脈の外に存在するこのローカルオブジェクトへの参照を単にplopしますか?表現が助けてくれましたか?たぶんそれをコンストラクタで渡そうとするのは間違った方法です。おそらく上と下の実装をオーバーライドすることになります(Actionへの関数ハンドルのようになり、呼び出すことはできますか? IEnumerable?)。

私はアセンブリとILに精通しており、操作を見直した後、私がやろうとしていることをやり遂げることができないかもしれないと思っています。

これも可能ですか、もしそうなら、誰かが私を正しい方向に向けることができますか?

不思議なことに、完全なコードはhereです。

+0

あなたが近づくかどうかわからない:http://stackoverflow.com/q/8419839 –

+0

@Calohseそうではありません。ILを発行している時点で、私が持っているアクションに直接的に何らかの参照を出す方法は見つけられませんでしたが、その動作を静的な辞書に格納し、ダイナミックアセンブリで呼び出して呼び出します動的クラスの正しいActionを返す静的関数に戻って呼び出します。私はここでコードに答えを加えなければならないでしょう。 –

答えて

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); 
+0

@CALohseのコメントは、同じ種類の解決策につながった。問題は、私が動的に生成しているクラスのインスタンス化を制御しないということです。移行ランナー(MSBuildターゲットまたはexe)はクラスをインスタンス化し、常に引数のないコンストラクターを探します。 –

関連する問題