63

一般的に言えば、リフレクションを使用するとパフォーマンスに影響があることを認識しています。(実際、私自身は反省のファンではありません。これは純粋に学術的な質問です。)

次のようなクラスが存在するとします。

public class MyClass {
    public string GetName() {
        return "My Name";
    }
}

ここで我慢してください。MyClass呼び出された のインスタンスがあればx、 を呼び出すことができることを知っていますx.GetName()。さらに、Func<string>変数を に設定できますx.GetName

ここで私の質問です。上記のクラスが呼び出されていることを知らないとしましょうMyClass。あるオブジェクトを持っていますがx、それが何であるかわかりません。GetNameこれを行うことで、そのオブジェクトにメソッドがあるかどうかを確認できます。

MethodInfo getName = x.GetType().GetMethod("GetName");

nullgetNameではないとします。getName.ReturnType == typeof(string)次に、さらに ifとをチェックすることはできませんでしたか? この時点で、オブジェクトによって表されるメソッドが何らかの方法で , に確実にキャストできるgetName.GetParameters().Length == 0と確信できませんか?getNameFunc<string>

があることに気付きました。また、次のようなものをいつでも作成MethodInfo.Invokeできることにも気付きました。Func<string>

Func<string> getNameFunc = () => getName.Invoke(x, null);

私が求めているのは、オブジェクトからそれが表す実際のメソッドに移動し、 processでリフレクションのパフォーマンス コストが発生する方法があるかどうかだと思いますが、その後はメソッドを直接呼び出すことができます (たとえば、 aまたは類似のもの)パフォーマンスの低下なし。MethodInfoFunc<string>

私が想定しているのは、次のようなものです。

// obviously this would throw an exception if GetActualInstanceMethod returned
// something that couldn't be cast to a Func<string>
Func<string> getNameFunc = (Func<string>)getName.GetActualInstanceMethod(x);

(私はそれが存在しないことを理解しています;私はそのようなものがあるかどうか疑問に思っています.)

4

6 に答える 6

42

この種の回答は、以前の回答に取って代わります。これは、少し長いルートですが、迅速なメソッド呼び出しを提供し、他のいくつかの回答とは異なり、さまざまなインスタンスを通過できるためです (複数のインスタンスに遭遇する場合)。同じタイプです)。それを望まない場合は、下部にある私の更新を確認してください (または、Ben M の回答を見てください)。

これがあなたが望むことをするテストメソッドです:

public class TestType
{
  public string GetName() { return "hello world!"; }
}

[TestMethod]
public void TestMethod2()
{
  object o = new TestType();

  var input = Expression.Parameter(typeof(object), "input");
  var method = o.GetType().GetMethod("GetName", 
    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
  //you should check for null *and* make sure the return type is string here.
  Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));

  //now build a dynamic bit of code that does this:
  //(object o) => ((TestType)o).GetName();
  Func<object, string> result = Expression.Lambda<Func<object, string>>(
    Expression.Call(Expression.Convert(input, o.GetType()), method), input).Compile();

  string str = result(o);
  Assert.AreEqual("hello world!", str);
}

デリゲートを一度構築したら、ディクショナリにキャッシュできます。

Dictionary<Type, Func<object, string>> _methods;

あとは、着信オブジェクトの Type (GetType() から) をキーとして使用して、それをディクショナリに追加するだけです。将来的には、最初にディクショナリに既製のデリゲートがあるかどうかを確認し (そして、そうであればそれを呼び出します)、そうでない場合は、最初にそれを構築し、追加してから呼び出します。

ちなみに、これは DLR が動的ディスパッチ メカニズムに対して行うことを非常に単純化したバージョンです (C# の用語では、'dynamic' キーワードを使用する場合)。

そして最後に

数人が言及しているように、受け取ったオブジェクトにバインドされた Func を直接ベイクしたい場合は、次のようにします。

[TestMethod]
public void TestMethod3()
{
  object o = new TestType();

  var method = o.GetType().GetMethod("GetName", 
    System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);

  Assert.IsFalse(method == null && !method.ReturnType.Equals(typeof(string)));

  //this time, we bake Expression.Constant(o) in.
  Func<string> result = Expression.Lambda<Func<string>>(
   Expression.Call(Expression.Constant(o), method)).Compile();

  string str = result(); //no parameter this time.
  Assert.AreEqual("hello world!", str);
}

ただし、式ツリーが破棄oされたら、スコープ内にとどまることを確認する必要があることに注意してください。そうしないと、厄介な結果が生じる可能性があります。最も簡単な方法は、デリゲートの有効期間中、ローカル参照 (おそらくクラス インスタンス内) を保持することです。(Ben M のコメントにより削除)

于 2010-05-29T00:11:15.507 に答える
21

はい、可能です:

Func<string> func = (Func<string>)
                     Delegate.CreateDelegate(typeof(Func<string>), getName);
于 2010-05-29T00:12:27.153 に答える
17

これが式ツリーを構築することによる私の答えです。他の回答とは異なり、結果 ( getNameFunc) は元のインスタンスにバインドされた関数であり、パラメーターとして渡す必要はありません。

class Program
{
    static void Main(string[] args)
    {
        var p = new Program();
        var getNameFunc = GetStringReturningFunc(p, "GetName");
        var name = getNameFunc();
        Debug.Assert(name == p.GetName());
    }

    public string GetName()
    {
        return "Bob";
    }

    static Func<string> GetStringReturningFunc(object x, string methodName)
    {
        var methodInfo = x.GetType().GetMethod(methodName);

        if (methodInfo == null ||
            methodInfo.ReturnType != typeof(string) ||
            methodInfo.GetParameters().Length != 0)
        {
            throw new ArgumentException();
        }

        var xRef = Expression.Constant(x);
        var callRef = Expression.Call(xRef, methodInfo);
        var lambda = (Expression<Func<string>>)Expression.Lambda(callRef);

        return lambda.Compile();
    }
}
于 2010-05-29T00:27:11.283 に答える
11

これを行う最も簡単な方法は次のDelegate.CreateDelegateとおりです。

Func<string> getNameFunc = (Func<string>) Delegate.CreateDelegate(
                                           typeof(Func<string>), x, getName);

これは にバインドgetNameFuncされるxため、それぞれに対してx新しいデリゲート インスタンスを作成する必要があることに注意してください。Expressionこのオプションは、ベースの例よりもはるかに複雑ではありません。ただし、式ベースの例では、 のFunc<MyClass, string> getNameFuncForAnyインスタンスごとに再利用できる を一度作成することができますMyClass

このような getNameFuncForAny を作成するには、次のようなメソッドが必要です。

public Func<MyClass, string> GetInstanceMethod(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(typeof(MyClass), "it");
    return Expression.Lambda<Func<MyClass, string>>(
        Expression.Call(x, method), x).Compile();
}

次のように使用できます。

Func<MyClass, string> getNameFuncForAny = GetInstanceMethod(getName);

MyClass x1 = new MyClass();
MyClass x2 = new MyClass();

string result1 = getNameFuncForAny(x1);
string result2 = getNameFuncForAny(x2);

に縛られたくない場合はFunc<MyClass, string>、定義できます

public TDelegate GetParameterlessInstanceMethod<TDelegate>(MethodInfo method)
{
    ParameterExpression x = Expression.Parameter(method.ReflectedType, "it");
    return Expression.Lambda<TDelegate>(
        Expression.Call(x, method), x).Compile();
}
于 2010-05-29T01:23:35.410 に答える
1

このメソッドを呼び出すラムダを表す式ツリーを構築しCompile()てから、それ以降の呼び出しが標準のコンパイル済み呼び出しと同じくらい高速になるようにすることができます。

別の方法として、MSDN の優れた記事に基づいてこのメソッドをかなり前に作成しました。これは、IL を使用してラッパーを生成し、コードが生成されると、通常の呼び出しよりもオーバーヘッドがほとんどないため、MethodInfoよりも高速に呼び出すことができます。MethodInfo.DynamicInvoke

于 2010-05-29T00:15:36.507 に答える
0

私の頭の中のアプローチの 1 つは、動的を使用することです。次に、次のようなことができます。

if( /* This method can be a Func<string> */)
{
    dynamic methodCall = myObject;
    string response = methodCall.GetName();
}
于 2010-05-29T00:10:43.037 に答える