2012-10-27 7 views
6


Introduzionedinamicamente creare l'oggetto utilizzando la riflessione, i metodi e lambda incatenati espressioni

La mia applicazione crea un'istanza di un oggetto utilizzando il metodo di concatenamento in modo che viene generato e configurato in questo modo:

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


Problema

Ho un requiremen Per generare dinamicamente questo oggetto in fase di runtime, i metodi concatenati necessari per la configurazione verranno determinati in fase di runtime, quindi tutto deve essere assemblato dinamicamente al volo. In passato ho usato la riflessione per creare oggetti semplici come new Car("Ferrari", 2, "Red") - Mi piace - ma mai niente con metodi concatenati contenenti espressioni lambda come parametri - questi due fattori mi hanno davvero bloccato. Ho esaminato gli alberi di espressione e credo che questo sia parte della soluzione per creare i parametri di espressione dinamica ma sono totalmente bloccato cercando di capire come cucirli insieme alla riflessione per creare l'oggetto di base e i metodi concatenati aggiuntivi.


ringraziamento e apprezzamento

in anticipo per il tempo dedicato a guardare il mio problema e per qualsiasi consiglio o informazioni che potrebbe essere in grado di fornire.


UPDATE: Conclusione

Molte grazie a dasblinkenlight e Jon Skeet per le loro risposte. Ho scelto la risposta di dasblinkenlight perché il suo esempio di codice mi ha messo fuori gioco immediatamente. Per il metodo di concatenamento ho fondamentalmente usato lo stesso approccio di ciclo nella risposta accettata, quindi non ripeterò quel codice ma sotto è il codice che ho scritto per convertire dinamicamente le chiamate del metodo dell'albero di espressione in delegati di azione che potrebbero essere eseguiti tramite la riflessione Invoke() come delineato nella risposta di dasblinkenlight. Questo, come ha sottolineato Jon, era davvero il nocciolo del problema.

Classe di supporto per memorizzare i metadati del metodo.

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


Metodo statico per assemblare una chiamata di metodo espressione lambda e poi tornare come un delegato azione da eseguire altrove (passato come argomento al Invoke() nel mio caso).

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

risposta

2

Siete di fronte due problemi distinti:

  • Invocare metodi incatenati, e
  • Invocare metodi che accettano lambda come parametri affare

Let con i due separatamente.

Diciamo che si dispone delle seguenti informazioni:

  • A ConstructorInfo che rappresenta il primo elemento della catena (il costruttore)
  • Un array di oggetti che rappresentano i parametri del costruttore
  • Una serie di MethodInfo oggetti - uno per ogni funzione concatenata
  • Un array di matrici di oggetti che rappresentano i parametri di ciascuna funzione concatenata

Poi il processo di costruzione il risultato sarebbe simile a questa:

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

Una volta che il ciclo è finito, il vostro res contiene un oggetto configurato.

Il codice sopra riportato presuppone che non vi siano metodi generici tra i metodi concatenati; se alcuni dei metodi sono generici, è necessario un ulteriore passaggio per creare un'istanza chiamabile di un metodo generico prima di chiamare Invoke.

Ora guardiamo le lambda. A seconda del tipo di lambda passato al metodo, è necessario passare un delegato con una firma particolare. Dovresti essere in grado di utilizzare la classe System.Delegate per convertire i metodi in delegati chiamabili. Potrebbe essere necessario creare metodi di supporto che implementino i delegati di cui hai bisogno. È difficile dire come senza vedere i metodi esatti che devi essere in grado di chiamare attraverso la riflessione. Potrebbe essere necessario andare su alberi di espressioni e ottenere istanze dopo averle compilate.La chiamata di x.Color("Red") sarebbe simile a questa:

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

Molte grazie das .. quindi utilizzando il codice fornito - potrebbero dell'oggetto d'azione "makeRed" essere memorizzati in "chainedArgs" ed eseguiti durante la "Invoke()" chiamata durante il ciclo di concatenamento? – mmacneil007

+0

@ mmacneil007 Assolutamente, questa è l'idea. 'L'azione ' è un delegato che può essere memorizzato in uno degli array all'interno dell'array 'chainedArgs' degli array, da passare al suo metodo corrispondente (nel tuo caso, sarebbe' OtherProperties'). – dasblinkenlight

+0

Eccellente, credo che questo mi abbia messo sulla strada giusta. Sto ancora avvolgendo la mia mente intorno agli alberi di espressione ma penso che l'approccio che hai delineato qui dovrebbe fare il trucco. Grazie ancora! – mmacneil007

1

ma mai nulla con metodi concatenati contenenti espressioni lambda come parametri

Beh, metodi incatenati sono il bit. È solo questione di usare il riflesso più volte.Per concatenare

foo.X().Y() 

è necessario:

  • metodo Get X dal tipo dichiarato di foo
  • Richiamare il metodo attraverso la riflessione utilizzando il valore della foo come destinazione delle chiamate, e ricordare il risultato (ad es. tmp)
  • Ottenere il metodo Y dal tipo dichiarato del tipo di ritorno di X (vedi MethodInfo.ReturnType)
  • richiamare il metodo attraverso la riflessione con il risultato precedente (tmp) come la chiamata di destinazione

L'espressione lambda è più difficile - in realtà dipende da come si sta andando ad essere fornita l'espressione innanzitutto. Costruire un delegato per eseguire un'espressione ragionevolmente arbitraria non è troppo difficile usando expression trees e poi chiamando LambdaExpression.Compile, ma è necessario sapere cosa si sta facendo.

Problemi correlati