2010-05-29 5 views
48

Ich bin mir im Klaren, dass es im Allgemeinen Auswirkungen der Reflexion auf die Leistung gibt. (Ich selbst bin kein Fan von Reflexion überhaupt, eigentlich, das eine rein akademische Frage.)Können Sie ein Func <T> (oder ein ähnliches) von einem MethodInfo-Objekt abrufen?

Es wäre eine Klasse gibt es das wie folgt aussieht:

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

Bär mit mir hier. Ich weiß, dass, wenn ich eine Instanz von MyClassx genannt habe, kann ich x.GetName() anrufen. Außerdem könnte ich eine Func<string> Variable auf x.GetName setzen.

Jetzt ist hier meine Frage. Lassen Sie uns sagen, ich nicht wissen, dass die oben genannte Klasse heißt MyClass; Ich habe ein Objekt, x, aber ich habe keine Ahnung, was es ist. Ich konnte überprüfen, um zu sehen, ob das Objekt eine GetName Methode hat dies tun:

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

Angenommen getName ist nicht null. Dann konnte ich nicht überprüfen, ob getName.ReturnType == typeof(string) und getName.GetParameters().Length == 0, und an dieser Stelle wäre ich nicht ganz sicher, dass die Methode von meinem getName Objekt definitiv auf eine Func<string> gegossen werden könnte, irgendwie?

Ich weiß, es gibt eine MethodInfo.Invoke, und ich weiß auch, konnte ich immer erstellen Func<string> wie:

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

Ich denke, was ich frage ist, ob es eine Möglichkeit gibt, von ein MethodInfo zu gehen Objekt bis die tatsächliche Methode, die es darstellt, die Kosten für die Reflexion in der Prozess Prozess, aber nach dieser Punkt in der Lage sein, die mich anrufen thod direkt (über, zum Beispiel, ein Func<string> oder etwas ähnliches) ohne eine Leistungseinbuße.

Was ich Vorstellungsvermögen könnte wie folgt aussehen: (. Ich weiß, dass es nicht gibt, ich frage mich, ob es etwas wie es)

// 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); 

+1

Auf die Kommentare, die Sie in Ihrem Schnitt machen - Sie werden eine enorme Geschwindigkeitserhöhung mit Lösungen wie thi sehen s, weil zwischen einem dynamisch kompilierten Delegierten und einem statisch kompilierten Delegierten nur sehr wenig Unterschiede bestehen; Sobald der Overhead der Kompilierung ausgeklammert ist. Seit ich die Ausdrucksbaumstruktur "entdeckt" habe, benutze ich sie überall und würde sie wahrscheinlich als meine Nr.1 ​​Eigenschaft von .Net 3.5 bezeichnen. In Version 4 ist es sogar noch besser, da Sie Code mit mehreren Anweisungen schreiben können, der aufgrund der vom DLR benötigten Erweiterungen benötigt wird. –

Antwort

33

Diese Art ersetzt meine vorherige Antwort, weil das, obwohl es eine etwas längere Strecke ist - gibt Ihnen einen schnellen Methodenaufruf und, im Gegensatz zu einigen der anderen Antworten, können Sie Durchlaufen Sie verschiedene Instanzen (für den Fall, dass Sie mehrere Instanzen des gleichen Typs treffen). WENN du das nicht willst, überprüfe mein Update unten (oder sieh dir die Antwort von Ben M an).

Hier ist ein Testverfahren, das tut, was Sie wollen:

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); 
} 

Sobald Sie den Delegierten einmal bauen - Sie können es in einem Dictionary-Cache kann:

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

Alles, was Sie dann ist es tun hinzufügen in das Wörterbuch mit dem Typ des eingehenden Objekts (aus GetType()) als Schlüssel. In der Zukunft überprüfen Sie zuerst, ob Sie einen fertig gebackenen Delegaten im Wörterbuch haben (und rufen Sie ihn gegebenenfalls auf), andernfalls erstellen Sie ihn zuerst, fügen ihn hinzu und rufen ihn dann auf.

Übrigens ist dies eine sehr stark vereinfachte Version der Art von Dingen, die der DLR für seinen dynamischen Versandmechanismus tut (in C#, das ist, wenn Sie das 'dynamische' Schlüsselwort verwenden).

Und schließlich

Wenn, wie einige Leute schon erwähnt haben, Sie einfach eine Func gebunden direkt auf das Objekt backen wollen erhalten Sie dann Sie dies tun:

[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); 
} 

Hinweis, obwohl, sobald der Ausdruck Baum weggeworfen wird, müssen Sie sicherstellen, dass der o im Bereich bleibt, sonst könnten Sie einige böse Ergebnisse bekommen. Der einfachste Weg wäre, eine lokale Referenz (in einer Klasseninstanz, vielleicht) für die Lebensdauer Ihres Delegierten beizubehalten. (Entfernte als Folge von Ben M Kommentare)

+0

Beispiel zu folgen :) –

+0

Großartige Köpfe denken gleich. :-) Eine Frage allerdings - warum der Cast-Ausdruck? –

+0

Oh, aber bleib dran - er muss noch einen Parameter an deinen Lambda-Ausdruck übergeben. Ich nahm von der ursprünglichen Frage, dass die generierte Funktion an die Instanz gebunden werden sollte, ohne einen Parameter übergeben zu müssen. –

0

Einmaliger die Spitze meines Ansatzes wäre, dynamisch zu sein. Sie könnten dann so etwas wie folgt aus:

if(/* This method can be a Func<string> */) 
{ 
    dynamic methodCall = myObject; 
    string response = methodCall.GetName(); 
} 
14

Ja, das ist möglich:

Func<string> func = (Func<string>) 
        Delegate.CreateDelegate(typeof(Func<string>), getName); 
+3

Diese Antwort ist nicht instanzsensitiv –

+0

@Ben M - Genau das, was ich dachte - und warum ich mich entschieden habe, die Delegate.CreateDelegate-Operation aufzugeben - weil es auch keine Möglichkeit geben wird, den richtigen Parametertyp für den ersten Parameter zu übergeben. –

+1

Fair Punkte. Ich glaube jedoch nicht, dass Ihre Lösung unbedingt besser ist. Wenn Sie Ihren dynamisch kompilierten in vielen verschiedenen Instanzen aufrufen, können die hohen Kompilierungskosten für jede Instanz sehr viel teurer sein, als einfach die 'MethodInfo' aufzurufen. Wenn dies der Fall ist, ist es besser, GetName statisch zu machen und einen Verweis auf die Instanz (also einen expliziten "this" -Zeiger) zu verwenden, was zu einem 'Func JulianR

1

Sie einen Ausdruck Baum bauen könnte eine Lambda darstellt, diese Methode aufrufen und dann Compile() so dass weitere Anrufe nur sein so schnell wie standard compilierte Anrufe.

Alternativ schrieb ich this method eine gute Weile her, basierend auf einem großen MSDN-Artikel, die einen Wrapper IL Verwendung erzeugt schneller jede MethodInfo Art und Weise zu nennen, als mit MethodInfo.DynamicInvoke da, sobald der Code generiert wird, gibt es fast keine Overhead über einen normalen Anruf.

12

Hier ist meine Antwort, indem ich einen Ausdrucksbaum erstelle. Im Gegensatz zu den anderen Antworten ist das Ergebnis (getNameFunc) eine Funktion, die an die ursprüngliche Instanz gebunden ist, ohne sie als Parameter übergeben zu müssen.

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(); 
    } 
} 
+1

+1 - eine Konsolen-App, kein Test-Projekt ... besser kopieren/einfügen Wert! –

+0

Awesome Antwort, lehrte mich sehr (hatte nicht viel Arbeit mit Expression Trees zuvor). Ich gab es Andras, da seine Antwort etwas früher kam, aber das war auch sehr hilfreich. Vielen Dank! –

6

Der einfachste Weg dies zu tun ist durch Delegate.CreateDelegate:

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

Beachten Sie, dass diese getNameFunc an x bindet, so dass für jeden x Sie einen neuen Delegaten Instanz erstellen bräuchten. Diese Option ist viel weniger kompliziert als die Expression-basierten Beispiele. Mit den expressionsbasierten Beispielen ist es jedoch möglich, einmal eine Func<MyClass, string> getNameFuncForAny zu erstellen, die Sie für jede Instanz einer MyClass erneut verwenden können.

ein solches getNameFuncForAny So erstellen Sie eine Methode, wie

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(); 
} 

brauchen würde, die Sie wie so verwenden können:

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

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

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

Wenn Sie Func<MyClass, string> werden gebunden wollen nicht, Sie können definieren

public TDelegate GetParameterlessInstanceMethod<TDelegate>(MethodInfo method) 
{ 
    ParameterExpression x = Expression.Parameter(method.ReflectedType, "it"); 
    return Expression.Lambda<TDelegate>(
     Expression.Call(x, method), x).Compile(); 
} 
+1

Man könnte auch 'Delegate.CreateDelegate (typeof (Func ), getName)' - Ich glaube, es heißt ein "offener Delegat". –

Verwandte Themen