2012-10-27 11 views
6


Einführungdynamisch erstellen Objekt mit Reflexion, angekettet Methoden und Lambda-Ausdrücke

Meine Anwendung ein Objekt mit der Methode Verkettungs instanziiert, so dass es erzeugt wird und so konfiguriert ist, wie so:

var car = new Car("Ferrari").Doors(2).OtherProperties(x = x.Color("Red")); 


Problem

Ich habe eine Voraussetzung t Um dieses Objekt zur Laufzeit dynamisch zu erzeugen, werden die zur Konfiguration benötigten verketteten Methoden zur Laufzeit ermittelt, so dass alles dynamisch im laufenden Betrieb zusammengestellt werden muss. Ich habe Reflektion in der Vergangenheit verwendet, um einfache Objekte wie new Car("Ferrari", 2, "Red") zu erstellen - ich bin damit cool - aber nie etwas mit verketteten Methoden, die Lambda-Ausdrücke als Parameter enthalten - diese beiden Faktoren haben mich wirklich fest im Griff. Ich habe mir die Ausdrucksbäume angesehen und glaube, dass dies ein Teil der Lösung ist, um die dynamischen Ausdrucksparameter zu erstellen, aber ich bin total dabei, herauszufinden, wie man das zusammen mit der Reflexion zum Grundobjekt und den zusätzlichen verketteten Methoden zusammenfügt.


Dank und Anerkennung

im Voraus für die Zeit nehmen, um mein Problem zu suchen und für jede Anleitung oder Informationen, die Sie liefern können.


UPDATE: Fazit

Vielen Dank an dasblinkenlight und Jon Skeet für ihre Antworten. Ich habe dasblinkenlight's Antwort ausgewählt, weil sein Code-Sample mich sofort losgelöst hat. Für die Methodenverkettung habe ich grundsätzlich den gleichen Schleifenansatz in der akzeptierten Antwort verwendet, also werde ich diesen Code nicht wiederholen, aber unten ist der Code, den ich geschrieben habe, um Ausdruckbaummethodenaufrufe dynamisch in Aktionsdelegaten umzuwandeln, die dann wie beschrieben über die Reflektion Invoke() ausgeführt werden könnten in dasblinkenlight's Antwort. Dies war, wie Jon betonte, wirklich der Kern des Problems.

Hilfsklasse zum Speichern von Methodenmetadaten.

public struct Argument 
    { 
     public string TypeName; 
     public object Value; 
    } 

public class ExpressionTreeMethodCall 
{ 
    public string MethodName { get; set; } 
    public IList<Argument> Arguments { get; set; } 

    public ExpressionTreeMethodCall() 
    { 
     Arguments = new List<Argument>(); 
    } 
} 


Statische Methode einen Lambda-Ausdruck Methodenaufruf zu montieren und sie dann wieder als Aktionsdelegate wird an anderer Stelle ausgeführt (als Argument an Invoke() in meinem Fall).

public static Action<T> ConvertExpressionTreeMethodToDelegate<T>(ExpressionTreeMethodCall methodData) 
    {    
     ParameterExpression type = Expression.Parameter(typeof(T)); 

     var arguments = new List<ConstantExpression>(); 
     var argumentTypes = new List<Type>(); 

     foreach (var a in methodData.Arguments) 
     { 
      arguments.Add(Expression.Constant(a.Value)); 
      argumentTypes.Add(Type.GetType(a.TypeName)); 
     } 

     // Creating an expression for the method call and specifying its parameter. 
     MethodCallExpression methodCall = Expression.Call(type, typeof(T).GetMethod(methodData.MethodName, argumentTypes.ToArray()), arguments); 

     return Expression.Lambda<Action<T>>(methodCall, new[] { type }).Compile(); 
    } 

Antwort

2

Sie stehen vor zwei verschiedene Probleme:

  • Aufrufen gekettet Methoden und
  • Methoden aufrufen, die Lambda-Ausdrücke nehmen als

Lassen Sie uns beschäftigen sich mit den beiden separat Parameter.

Lassen Sie uns sagen, dass Sie die folgenden Informationen zur Verfügung haben:

  • A ConstructorInfo das erste Element in der Kette darstellt (der Konstruktor)
  • Ein Array von Objekten Parameter Konstruktor
  • Ein Array von MethodInfo darstellt Objekte - eine für jede verkettete Funktion
  • Ein Array von Objektarrays, die Parameter jeder verketteten Funktion darstellen

Dann wird der Prozess das Ergebnis der Konstruktion würde wie folgt aussehen:

ConstructorInfo constr = ... 
object[] constrArgs = ... 
MethodInfo[] chainedMethods = ... 
object[][] chainedArgs = ... 
object res = constr.Invoke(constrArgs); 
for (int i = 0 ; i != chainedMethods.Length ; i++) { 
    // The chaining magic happens here: 
    res = chainedMethods[i].Invoke(res, chainedArgs[i]); 
} 

Sobald die Schleife beendet ist, Ihre res enthält ein konfiguriertes Objekt.

Der obige Code geht davon aus, dass es unter den verketteten Methoden keine generischen Methoden gibt; Wenn einige der Methoden generisch sind, benötigen Sie einen zusätzlichen Schritt, um eine aufrufbare Instanz einer generischen Methode vor dem Aufruf von Invoke aufzurufen.

Jetzt schauen wir uns die Lambdas an. Abhängig vom Typ des Lambda, der an die Methode übergeben wird, müssen Sie einen Delegaten mit einer bestimmten Signatur übergeben. Sie sollten die Klasse System.Delegate verwenden können, um Methoden in aufrufbare Delegaten zu konvertieren. Möglicherweise müssen Sie Unterstützungsmethoden erstellen, die die von Ihnen benötigten Delegaten implementieren. Es ist schwer zu sagen, wie ohne die genauen Methoden zu sehen, die Sie durch Reflexion abrufen können. Möglicherweise müssen Sie nach Ausdrucksbaumstrukturen suchen und nach dem Kompilieren Func<...> Instanzen abrufen.Der Aufruf von x.Color("Red") würde wie folgt aussehen:

Expression arg = Expression.Parameter(typeof(MyCarType)); 
Expression red = Expression.Constant("Red"); 
MethodInfo color = typeof(MyCarType).GetMethod("Color"); 
Expression call = Expression.Call(arg, color, new[] {red}); 
var lambda = Expression.Lambda(call, new[] {arg}); 
Action<MyCarType> makeRed = (Action<MyCarType>)lambda.Compile(); 
+0

Vielen Dank das .. so Ihre bereitgestellt Code - könnte das „makeRed“ Aktionsobjekt während des „Invoke()“ Anrufs in „chainedArgs“ und ausgeführt gespeichert wird während die Kettenschleife? – mmacneil007

+0

@ mmacneil007 Absolut, das ist die Idee. 'Aktion ' ist ein Delegat, das in einem der Arrays innerhalb des 'chainedArgs' Arrays von Arrays gespeichert werden kann, um es an die entsprechende Methode zu übergeben (in Ihrem Fall wäre das' OtherProperties'). – dasblinkenlight

+0

Ausgezeichnet, ich glaube, das hat mich auf den richtigen Weg gebracht. Ich wickle meinen Kopf immer noch um die Bäume herum, aber ich denke, der Ansatz, den Sie hier skizziert haben, sollte den Zweck erfüllen. Danke noch einmal! – mmacneil007

1

aber nie etwas mit verketteten Methoden Lambda-Ausdrücke enthalten, als Parameter

Nun, angekettet Methoden das Bit sind. Das ist nur eine Frage der mehrfachen Reflexion.Zur Kette zusammen

foo.X().Y() 

Sie müssen:

  • Get-Methode X vom deklarierten Typ von foo
  • Rufen Sie die Methode durch Reflexion den Wert von foo als Anrufziel verwenden, und erinnere mich an die Ergebnis (z. B. tmp)
  • Methode Y von dem deklarierten Typ Rückgabetyp von 0 abrufen(siehe MethodInfo.ReturnType)
  • die Methode über Reflektion Rufen Sie das vorherige Ergebnis mit (tmp) als Anrufziel

Der Lambda-Ausdruck ist schwieriger - es hängt wirklich davon ab, wie du gehst den Ausdruck zur Verfügung gestellt werden an erster Stelle. Einen Delegaten zu erstellen, um einen vernünftig willkürlichen Ausdruck auszuführen, ist nicht schwer mit expression trees und dann Aufruf LambdaExpression.Compile, aber Sie müssen wissen, was Sie tun.

Verwandte Themen