2012-10-27 20 views
6


WprowadzenieDynamicznie tworzenie obiektu za pomocą refleksji, przykuty metody i lambda wyrażeń

Moja aplikacja tworzy instancję obiektu za pomocą metody łańcuchowym więc jest generowany i skonfigurowany tak:

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


Problem

Mam wymagającego t dynamicznie generować ten obiekt w środowisku wykonawczym - metody łańcuchowe potrzebne do konfiguracji zostaną określone w czasie wykonywania, więc wszystko musi być dynamicznie montowane w locie. Używałem w przeszłości refleksów do tworzenia prostych obiektów, takich jak new Car("Ferrari", 2, "Red") - jestem z tym fajna - ale nigdy nic z przykutymi metodami zawierającymi wyrażenia lambda jako parametry - te dwa czynniki naprawdę mnie zablokowały. Przyjrzałem się drzewom ekspresji i wierzę, że jest to część rozwiązania do tworzenia parametrów dynamicznego wyrażania, ale całkowicie utknąłem próbując wymyślić, jak połączyć to z odbiciem, aby utworzyć obiekt podstawowy i dodatkowe metody łańcuchowe.


Dzięki i Wrażenia

z góry za poświęcenie czasu, aby spojrzeć na mojego problemu i żadnych wskazówek lub informacji może być w stanie zapewnić.


UPDATE: Zawarcie

Wiele dzięki dasblinkenlight i Jon Skeet na ich odpowiedzi. Wybrałem odpowiedź dasblinkenlight, ponieważ jego próbka kodu wyłączyła mnie natychmiast. Dla metody łańcuchowej użyłem zasadniczo tego samego podejścia do pętli w zaakceptowanej odpowiedzi, więc nie powtórzę tego kodu, ale poniżej jest kod, który napisałem, aby dynamicznie konwertować wywołania metod drzewa wyrażeń do delegatów akcji, które mogłyby następnie zostać wykonane poprzez odbicie Invoke() jak opisano w odpowiedzi dasblinkenlight. To, jak zauważył Jon, było sednem problemu.

Klasa pomocnicza do przechowywania metadanych metody meta.

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


Metoda statyczna montaż Sposób wywołania ekspresji lambda i następnie zwraca go jako pełnomocnika działania wykonywanego wcześniej (przekazany jako argument Invoke() w swoim przypadku).

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

Odpowiedz

2

stoją dwa oddzielne problemy:

  • powołując łańcuchu metod i
  • Wywoływanie metod, które mają lambdy jako parametry transakcji

Chodźmy z nimi osobno.

Powiedzmy, że masz następujące informacje:

  • A ConstructorInfo reprezentujący pierwszy element w łańcuchu (konstruktor)
  • Tablica obiektów reprezentujących parametry konstruktorów
  • Tablica MethodInfo obiekty - po jednym dla każdej funkcji łańcuchowej
  • Tablica tablic obiektów reprezentujących parametry każdej funkcji łańcuchowej

Następnie proces konstruowania wynik będzie wyglądać następująco:

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

Gdy pętla się skończy, Twój res zawiera skonfigurowany obiekt.

Powyższy kod zakłada, że ​​wśród metod łańcuchowych nie ma żadnych ogólnych metod; jeśli niektóre z metod staną się ogólne, będziesz potrzebować dodatkowego kroku w tworzeniu wywoływanej instancji ogólnej metody przed wywołaniem Invoke.

Teraz spójrzmy na lambdas. W zależności od typu lambda, który jest przekazywany do metody, musisz przekazać delegata z określonym podpisem. Powinieneś być w stanie użyć klasy System.Delegate, aby przekonwertować metody na osoby uprawnione do delegowania. Konieczne może być utworzenie metod pomocy technicznej, które wdrożą wymaganych delegatów. Trudno powiedzieć, nie widząc dokładnych metod, które trzeba wywołać poprzez refleksję. Konieczne może być przejście do drzew wyrażeń i uzyskanie instancji Func<...> po ich skompilowaniu.Wezwanie x.Color("Red") będzie wyglądać następująco:

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

Dziękujemy das .. więc za pomocą dostarczonego kodu - z ewentualnego „makeRed” obiekt Action być przechowywane w „chainedArgs” i wykonywane podczas „Invoke()” podczas rozmowy pętla łączenia? – mmacneil007

+0

@ mmacneil007 Absolutnie, to jest idea. 'Działanie ' jest delegatem, który może być przechowywany w jednej z tablic wewnątrz tablicy tablic 'chainedArgs' i przekazywany do odpowiedniej metody (w twoim przypadku byłaby to" OtherProperties "). – dasblinkenlight

+0

Doskonale, wierzę, że to postawiło mnie na właściwej drodze. Wciąż owijając głowę wokół drzewek ekspresji, ale myślę, że podejście, które tu przedstawiłeś, powinno załatwić sprawę. Dzięki jeszcze raz! – mmacneil007

1

ale nigdy nic z metod W powiązanych zawierających wyrażenia lambda jako parametry

Cóż, przykuty metody są nieco. To kwestia wielokrotnego użycia refleksji.Do łańcucha razem

foo.X().Y() 

trzeba:

  • metody Get X od zadeklarowanego typu foo
  • wywołania metody poprzez odbicie przy użyciu wartości foo jako cel rozmowy, a pamiętam wynik (np. tmp)
  • Uzyskaj metodę Y z zadeklarowanego typu zwrotu z X (patrz MethodInfo.ReturnType)
  • wywołania metody poprzez odbicie stosując poprzedni wynik (tmp) jako cel wywołania

Wyrażenie lambda jest trudniejsze - to naprawdę zależy od tego jak masz zamiar być warunkiem wyrażenia na pierwszym miejscu. Budowanie delegata w celu wykonania rozsądnie arbitralnego wyrażenia nie jest zbyt trudne przy użyciu expression trees, a następnie wywołanie LambdaExpression.Compile, ale musisz wiedzieć, co robisz.

Powiązane problemy