2012-02-29 9 views
6

こんにちは私は同じ戻り値とパラメータとして受け取るMethodInfoと同じパラメータを持つ代理人を動的に作成する関数を作成しようとしており、これは同じパラメータ名!私がこれまでにやったパラメータ名を使用して代理人を動的に作成する

は同じパラメータ型を受け取り、METHODINFOと同じ戻り値を持つラムダを返す関数を作成することですが、それはパラメータ名を持っていない:

static void Example() 
    { 
     Person adam = new Person(); 
     MethodInfo method = typeof(Person).GetMethod("Jump"); 
     Delegate result = CreateDelegate(adam, method); 
     result.DynamicInvoke((uint)4, "Yeahaa"); 
    } 

    private static Delegate CreateDelegate(object instance, MethodInfo method) 
    { 
     var parametersInfo = method.GetParameters(); 
     Expression[] expArgs = new Expression[parametersInfo.Length]; 
     List<ParameterExpression> lstParamExpressions = new List<ParameterExpression>(); 
     for (int i = 0; i < expArgs.Length; i++) 
     { 
      expArgs[i] = Expression.Parameter(parametersInfo[i].ParameterType, parametersInfo[i].Name); 
      lstParamExpressions.Add((ParameterExpression)expArgs[i]); 
     } 

     MethodCallExpression callExpression = Expression.Call(Expression.Constant(instance), method, expArgs); 
     LambdaExpression lambdaExpression = Expression.Lambda(callExpression, lstParamExpressions); 

     return lambdaExpression.Compile(); 
    } 

    private class Person 
    { 
     public void Jump(uint height, string cheer) 
     { 
      Console.WriteLine("Person jumped " + height + " "+ cheer); 
     } 
    } 

んどのように私はそれを行うことができます誰も提案がありますか? 私がパラメータ名を気にする理由は、私がパラメータ名でデリゲートをアクティブにすることができるので、私はこのように呼び出すことができる(cheer = "YAY!"、height = 3) 私のアプリケーションはPythonに統合されているので、DynamicInvokeを使わないとどうやってやることができるのでしょうか?また、パラメータ名が非常に重要な理由です。 と '='と ':'を書かなかった理由)

答えて

6

デリゲートを動的に作成するには、Reflection.Emitを使用します。デリゲートは.NETの特別な型であるため、作成するコードはあまり明確ではありません。以下はExpression.Lambda()で使用されるメソッドの反映されたコードに基づいています。状況でカスタムデリゲートタイプを作成します。ActionまたはFuncデリゲートが利用可能ではありません(mo 17以上のパラメータ、またはrefまたはoutのパラメータ)。

class DelegateTypeFactory 
{ 
    private readonly ModuleBuilder m_module; 

    public DelegateTypeFactory() 
    { 
     var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
      new AssemblyName("DelegateTypeFactory"), AssemblyBuilderAccess.RunAndCollect); 
     m_module = assembly.DefineDynamicModule("DelegateTypeFactory"); 
    } 

    public Type CreateDelegateType(MethodInfo method) 
    { 
     string nameBase = string.Format("{0}{1}", method.DeclaringType.Name, method.Name); 
     string name = GetUniqueName(nameBase); 

     var typeBuilder = m_module.DefineType(
      name, TypeAttributes.Sealed | TypeAttributes.Public, typeof(MulticastDelegate)); 

     var constructor = typeBuilder.DefineConstructor(
      MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, 
      CallingConventions.Standard, new[] { typeof(object), typeof(IntPtr) }); 
     constructor.SetImplementationFlags(MethodImplAttributes.CodeTypeMask); 

     var parameters = method.GetParameters(); 

     var invokeMethod = typeBuilder.DefineMethod(
      "Invoke", MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Public, 
      method.ReturnType, parameters.Select(p => p.ParameterType).ToArray()); 
     invokeMethod.SetImplementationFlags(MethodImplAttributes.CodeTypeMask); 

     for (int i = 0; i < parameters.Length; i++) 
     { 
      var parameter = parameters[i]; 
      invokeMethod.DefineParameter(i + 1, ParameterAttributes.None, parameter.Name); 
     } 

     return typeBuilder.CreateType(); 
    } 

    private string GetUniqueName(string nameBase) 
    { 
     int number = 2; 
     string name = nameBase; 
     while (m_module.GetType(name) != null) 
      name = nameBase + number++; 
     return name; 
    } 
} 

あなたはパフォーマンスを気にした場合、あなたが何度も同じデリゲート型を作成しないように、あなたは、いくつかの種類のキャッシュを作成することもできます。

あなたのコード内の唯一の変更はlambdaExpressionを作成する行になります:

LambdaExpression lambdaExpression = Expression.Lambda(
    s_delegateTypeFactory.CreateDelegateType(method), 
    callExpression, lstParamExpressions); 

しかし、あなたは実際にはまったくExpression秒に対処する必要はありません。 Delegate.CreateDelegate()は十分です:

private static Delegate CreateDelegate(object instance, MethodInfo method) 
{ 
    return Delegate.CreateDelegate(
     s_delegateTypeFactory.CreateDelegateType(method), instance, method); 
} 
+0

はありがとう、私は今日早く試してみました。 私はあなたのコードの大部分を理解していますが、名前と名前で何をしているのか分かりませんでした。タイプベースの名前+タイプの名前と等しいのはなぜですか? どうもありがとうございました –

+0

@UchihaMadara、同じアセンブリ内で同じ名前の2つの型を持つことができないので、型名が一意であることを確認する方法です。そして、2番はそこにありますので、名前は 'PersonJump'、' PersonJump2'、 'PersonJump3'などのようになります。 – svick

+0

私はそれがとてもスマートだと思っています、ありがとう! =] –

0

オープンソースのフレームワークImpromptuInterface(nuget経由v5.6.7)DLR currying/partialは、私は限り、あなたは文字通りのデリゲートを必要としないとして、この場合に働くだろうと思うの実装を適用しています。ここで

がそれを作成し、それを呼び出すC#のバージョンである:

dynamic jump =Impromptu.Curry(adam).Jump(); 
jump(cheer:"yay", height:(uint)3); 

のでjumpがリテラルデリゲートではありません、あなたはそれを反映することはできませんが、それが委任したとあるかのように、あなたはそれを直接呼び出すことができますそれはDLRオブジェクトなので、私の推測では、Pythonではまったく同じように動作するということです。

0

私はこの問題を解決するための良い方法につまずいてきた、それは静的メソッドに委譲するために次のようになります。

private static Delegate CreateDelegate(MethodInfo method) { 
    var paramTypes = method.GetParameters().Select(p => p.ParameterType); 

    Type delegateType = Expression.GetDelegateType(paramTypes.Append(method.ReturnType).ToArray()); 

    return Delegate.CreateDelegate(delegateType, method, true); 
} 

それは、この拡張メソッドを使用しています。

public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> collection, TSource element) { 
    if (collection == null) throw new ArgumentNullException("collection"); 

    foreach (TSource element1 in collection) yield return element1; 
    yield return element; 
} 
関連する問題