8

作成時にインスタンスを指定せずに、インスタンスメソッドのデリゲートを作成できますか?つまり、メソッドが呼び出されるインスタンスを最初のパラメーターとして受け取る「静的」デリゲートを作成できますか?

たとえば、リフレクションを使用して次のデリゲートを作成するにはどうすればよいですか?

Func<int, string> = i=>i.ToString();

methodInfo.Invokeを使用できることは知っていますが、これは遅く、呼び出されるまで型の正しさをチェックしません。

MethodInfo特定の静的メソッドのを持っている場合、を使用してデリゲートを構築することが可能Delegate.CreateDelegate(delegateType, methodInfo)であり、静的メソッドのすべてのパラメーターはフリーのままです。

Jon Skeetが指摘したように、メソッドが参照型で非仮想である場合は、同じものを適用してインスタンスメソッドのオープンデリゲートを作成できます。仮想メソッドで呼び出すメソッドを決定するのは難しいので、それほど簡単ではなく、値型はまったく機能しないように見えます。

値型の場合、CreateDelegate非常に奇妙な動作を示します。

var func37 = (Func<CultureInfo,string>)(37.ToString);
var toStringMethod = typeof(int).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, new Type[] {typeof(CultureInfo) }, null);
var func42 = (Func<CultureInfo,string>)Delegate.CreateDelegate(typeof(Func<CultureInfo,string>), 42, toStringMethod,true);
Console.WriteLine( object.ReferenceEquals(func37.Method,func42.Method)); //true
Console.WriteLine(func37.Target);//37
Console.WriteLine(func42.Target);//42
Console.WriteLine(func37(CultureInfo.InvariantCulture));//37
Console.WriteLine(func42(CultureInfo.InvariantCulture));//-201040128... WTF?

インスタンスメソッドが値型に属している場合、ターゲットオブジェクトとしてCreateDelegatewithを呼び出すと、バインディング例外がスローされます(これは参照型で機能します)。null

数年後のフォローアップ:私の例ではなく、誤ってバインドされたターゲットがfunc42(CultureInfo.InvariantCulture);戻ってきたのは、リモートでコードが実行される可能性のあるメモリの破損でした(cve-2010-1898)。これは、2010年にms10-060セキュリティアップデートで修正されました。現在のフレームワークは42を正しく印刷します!それはこの質問に答えるのを簡単にすることはありませんが、例の特に奇妙な振る舞いを説明しています。"-201040128""42"

4

4 に答える 4

12

2つの理由から、実際には特にトリッキーな例を選択しました。

  • objectToString()は、から継承されたがでオーバーライドされた仮想メソッドInt32です。
  • intは値型であり、値型とインスタンスメソッドに関しては奇妙なルールがDelegate.CreateDelegate()あります-基本的に最初の有効なパラメータはref intではなくになりますint

ただし、次の例はString.ToUpper、これらの問題のいずれも発生していません。

using System;
using System.Reflection;

class Test
{
    static void Main()
    {
        MethodInfo method = typeof(string).GetMethod
            ("ToUpper", BindingFlags.Instance | BindingFlags.Public,
             null, new Type[]{}, null);

        Func<string, string> func = (Func<string, string>)
            Delegate.CreateDelegate(typeof(Func<string, string>),
                                    null,
                                    method);

        string x = func("hello");

        Console.WriteLine(x);
    }
}

それがあなたにとって十分であるなら、素晴らしい...あなたが本当に望むならint.ToString、私はもう少し頑張らなければならないでしょう:)

参照によって最初のパラメーターを受け取る新しいデリゲート型を使用した、値型の例を次に示します。

using System;
using System.Reflection;

public struct Foo
{
    readonly string value;

    public Foo(string value)
    {
        this.value = value;
    }

    public string DemoMethod()
    {
        return value;
    }
}

class Test
{
    delegate TResult RefFunc<TArg, TResult>(ref TArg arg);

    static void Main()
    {
        MethodInfo method = typeof(Foo).GetMethod
            ("DemoMethod", BindingFlags.Instance | BindingFlags.Public,
             null, new Type[]{}, null);
        RefFunc<Foo, string> func = (RefFunc<Foo, string>)
            Delegate.CreateDelegate(typeof(RefFunc<Foo, string>),
                                    null,
                                    method);

        Foo y = new Foo("hello");
        string x = func(ref y);

        Console.WriteLine(x);
    }
}
于 2009-07-31T12:57:16.687 に答える
3

よくわかりませんが、オープンデリゲートがお手伝いできるかもしれません。

Upd:最初のリンクが機能しない場合は、このリンクをたどってください。

于 2009-07-31T12:49:21.183 に答える
2

Lambdasを使用して、インスタンスメソッドの「ある程度」コンパイルされた静的ラッパーを取得できます。

以下のサンプルは、正確に高速ではありませんが、単純な動的呼び出しよりも大幅に高速である必要があります。

出力

100000 iterations took 4 ms 
1000000 iterations took 18 ms 
10000000 iterations took 184 ms

コード

class Program
{

   public sealed class Test
   {
      public String Data { get; set; }
      public override string ToString()
      {
         return Data;
      }
   }

   static void Main(string[] args)
   {
      TestRun(100000);
      TestRun(1000000);
      TestRun(10000000);
   }

   private static void TestRun(int iterations)
   {
      var toString = typeof(Test).GetMethod("ToString",
                                            BindingFlags.Instance
                                            | BindingFlags.Public,
                                            null,
                                            Type.EmptyTypes,
                                            null);
      var call = GetCall<Test, String>(toString);
      var tests
         = (from i in Enumerable.Range(1, iterations)
            select new Test { Data = "..." + i }).ToList();

      var sw = Stopwatch.StartNew();
      tests.ForEach(i => call(i));
      sw.Stop();
      Console.WriteLine("{0} iterations took {1} ms", iterations, sw.ElapsedMilliseconds);
   }

   private static Func<T, M> GetCall<T, M>(MethodInfo methodInfo)
   {
      var input = Expression.Parameter(typeof(T), "input");
      MethodCallExpression member = Expression.Call(input, methodInfo);
      var lambda = Expression.Lambda<Func<T, M>>(member, input);

      return lambda.Compile();
   }
}
于 2009-07-31T15:33:00.607 に答える
0

グーグの方法は、おそらく.NET4.0の「動的」タイプを使用することです。ただし、デリゲートにはインスタンスが必要です(非静的メソッドの場合)。問題は、ポリモーフィズムなどのために、最初はロックよりも複雑です...

于 2009-07-31T12:56:11.587 に答える