とても簡単に聞こえます。最近では、IL を手動で発行する必要さえありません :)
最も簡単な方法は、「動的に作成する」部分を無視することです。T4 テンプレートを使用するだけで、コンパイル時に自動的にクラスを作成できます。単体テストのみを考慮する場合、これは問題を解決するための非常に簡単な方法です。
ここで、タイプを実際に (実行時に) 動的に作成したい場合、これはもう少し複雑になります。
まず、必要なすべてのメソッドを含むインターフェイスを作成します。C# クラスはこのインターフェイスを直接実装するだけですが、このインターフェイスに準拠するヘルパー クラスを生成します。
次に、ヘルパー クラスを作成します。
var assemblyName = new AssemblyName("MyDynamicAssembly");
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module");
var typeBuilder = moduleBuilder.DefineType("MyNewType", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes, typeof(YourClassBase), new[] { typeof(IYourInterface) } );
では、これらTypeBuilder
すべてのメソッドを定義できるので、次にそれを行いましょう。
// Get all the methods in the interface
foreach (var method in typeof(IYourInterface).GetMethods())
{
var parameters = method.GetParameters().Select(i => i.ParameterType).ToArray();
// We can only compile lambda expressions into a static method, so we'll have this helper. this is going to be YourClassBase.
var helperMethod = typeBuilder.DefineMethod
(
"s:" + method.Name,
MethodAttributes.Private | MethodAttributes.Static,
method.ReturnType,
new [] { method.DeclaringType }.Union(parameters).ToArray()
);
// The actual instance method
var newMethod =
typeBuilder.DefineMethod
(
method.Name,
MethodAttributes.Public | MethodAttributes.Virtual,
method.ReturnType,
parameters
);
// Compile the static helper method
Build(method).CompileToMethod(helperMethod);
// We still need raw IL to call the helper method
var ilGenerator = newMethod.GetILGenerator();
// First argument is (YourClassBase)this, then we emit all the other arguments.
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Castclass, typeof(YourClassBase));
for (var i = 0; i < parameters.Length; i++) ilGenerator.Emit(OpCodes.Ldarg, i + 1);
ilGenerator.Emit(OpCodes.Call, helperMethod);
ilGenerator.Emit(OpCodes.Ret);
// "This method is an implementation of the given IYourInterface method."
typeBuilder.DefineMethodOverride(newMethod, method);
}
ヘルパー メソッド本体を作成するために、次の 2 つのヘルパー メソッドを使用しています。
LambdaExpression Build(MethodInfo methodInfo)
{
// This + all the method parameters.
var parameters =
new [] { Expression.Parameter(typeof(YourClassBase)) }
.Union(methodInfo.GetParameters().Select(i => Expression.Parameter(i.ParameterType)))
.ToArray();
return
Expression.Lambda
(
Expression.Call
(
((Func<MethodInfo, YourClassBase, object[], object>)InvokeInternal).Method,
Expression.Constant(methodInfo, typeof(MethodInfo)),
parameters[0],
Expression.NewArrayInit(typeof(object), parameters.Skip(1).Select(i => Expression.Convert(i, typeof(object))).ToArray())
),
parameters
);
}
public static object InvokeInternal(MethodInfo method, YourClassBase @this, object[] arguments)
{
var script = @"
var calc = new Com.Example.FormCalculater();
var result = calc.{0}({1});";
script = string.Format(script, method.Name, string.Join(", ", arguments.Select(i => Convert.ToString(i))));
@this.ScriptEngine.Evaluate(script);
return (object)Convert.ChangeType(@this.ScriptEngine.Evaluate("result"), method.ReturnType);
}
必要に応じて、これをより具体的にすることもできます (指定されたメソッドにより適した式ツリーを生成します) が、これにより多くの手間が省け、困難な作業のほとんどに C# を使用できるようになります。
すべてのメソッドに戻り値があると仮定しています。そうでない場合は、それに合わせて調整する必要があります。
そして最後に:
var resultingType = typeBuilder.CreateType();
var instance = (IYourInterface)Activator.CreateInstance(resultingType);
var init = (YourClassBase)instance;
init.ScriptEngine = new ScriptEngine();
var result = instance.Add(12, 30);
Assert.AreEqual(42M, result);
完全を期すためにIYourInterface
、YourClassBase
私が使用したのは次のとおりです。
public interface IYourInterface
{
decimal Add(decimal x, decimal y);
}
public abstract class YourClassBase
{
public ScriptEngine ScriptEngine { get; set; }
}
ただし、可能であれば、テキスト テンプレートを使用してコンパイル時にソース コードを生成することを強くお勧めします。動的コードは、デバッグ (そしてもちろん書き込み) が難しい傾向があります。一方、テンプレートからこのようなものを生成するだけの場合は、生成されたヘルパー クラス全体がコードで表示されます。