最近、非常に似たようなこと (メソッド呼び出しの中継) を行う必要がありました。実行時にメソッド呼び出しを動的に転送する型を生成することになりました。
あなたのユースケースでは、実装は次のようになります。最初に、サービスを記述するインターフェースを作成します。コード内でサービスを呼び出したい場合は、このインターフェイスを使用します。
public interface IMyService
{
[ServiceImport("service_name")]
string ServiceName(int arg1, string arg2);
}
次に、コードを実行して、このインターフェイスを動的に実装するクラスを生成します。
// Get handle to the method that is going to be called.
MethodInfo executeMethod = typeof(Services).GetMethod("Execute");
// Create assembly, module and a type (class) in it.
AssemblyName assemblyName = new AssemblyName("MyAssembly");
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run, (IEnumerable<CustomAttributeBuilder>)null);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MyModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType("MyClass", TypeAttributes.Class | TypeAttributes.Public, typeof(object), new Type[] { typeof(IMyService) });
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
// Implement each interface method.
foreach (MethodInfo method in typeof(IMyService).GetMethods())
{
ServiceImportAttribute attr = method
.GetCustomAttributes(typeof(ServiceImportAttribute), false)
.Cast<ServiceImportAttribute>()
.SingleOrDefault();
var parameters = method.GetParameters();
if (attr == null)
{
throw new ArgumentException(string.Format("Method {0} on interface IMyService does not define ServiceImport attribute."));
}
else
{
// There is ServiceImport attribute defined on the method.
// Implement the method.
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
method.Name,
MethodAttributes.Public | MethodAttributes.Virtual,
CallingConventions.HasThis,
method.ReturnType,
parameters.Select(p => p.ParameterType).ToArray());
// Generate the method body.
ILGenerator methodGenerator = methodBuilder.GetILGenerator();
LocalBuilder paramsLocal = methodGenerator.DeclareLocal(typeof(object[])); // Create the local variable for the params array.
methodGenerator.Emit(OpCodes.Ldc_I4, parameters.Length); // Amount of elements in the params array.
methodGenerator.Emit(OpCodes.Newarr, typeof(object)); // Create the new array.
methodGenerator.Emit(OpCodes.Stloc, paramsLocal); // Store the array in the local variable.
// Copy method parameters to the params array.
for (int i = 0; i < parameters.Length; i++)
{
methodGenerator.Emit(OpCodes.Ldloc, paramsLocal); // Load the params local variable.
methodGenerator.Emit(OpCodes.Ldc_I4, i); // Value will be saved in the index i.
methodGenerator.Emit(OpCodes.Ldarg, (short)(i + 1)); // Load value of the (i + 1) parameter. Note that parameter with index 0 is skipped, because it is "this".
if (parameters[i].ParameterType.IsValueType)
{
methodGenerator.Emit(OpCodes.Box, parameters[i].ParameterType); // If the parameter is of value type, it needs to be boxed, otherwise it cannot be put into object[] array.
}
methodGenerator.Emit(OpCodes.Stelem, typeof(object)); // Set element in the array.
}
// Call the method.
methodGenerator.Emit(OpCodes.Ldstr, attr.Name); // Load name of the service to execute.
methodGenerator.Emit(OpCodes.Ldloc, paramsLocal); // Load the params array.
methodGenerator.Emit(OpCodes.Call, executeMethod); // Invoke the "Execute" method.
methodGenerator.Emit(OpCodes.Ret); // Return the returned value.
}
}
Type generatedType = typeBuilder.CreateType();
// Create an instance of the type and test it.
IMyService service = (IMyService)generatedType.GetConstructor(new Type[] { }).Invoke(new object[] { });
service.ServiceName(1, "aaa");
このソリューションはおそらく少し面倒ですが、自分でコードを作成する手間を省きたい場合は、非常にうまく機能します。動的型の作成に関連するパフォーマンス ヒットがあることに注意してください。ただし、これは通常、初期化中に行われ、ランタイムにあまり影響を与えるべきではありません。
あるいは、コンパイル時にコードを生成できるPostSharpを検討することをお勧めします。ただし、有料の商用ソリューションです。