2013-09-06 10 views
13

mam następujące klasyGet wartość parametru z Expression Linq

public class MyClass 
{ 
    public bool Delete(Product product) 
    { 
     // some code. 
    } 
} 

Teraz mam klasy pomocnika, który wygląda tak

public class Helper<T, TResult> 
{ 

    public Type Type; 
    public string Method; 
    public Type[] ArgTypes; 
    public object[] ArgValues; 

    public Helper(Expression<Func<T, TResult>> expression) 
    { 
     var body = (System.Linq.Expressions.MethodCallExpression)expression.Body; 

     this.Type = typeof(T); 
     this.Method = body.Method.Name; 
     this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray(); 
     this.ArgValues = ??? 
    } 
} 

Pomysł ist użyć tego kodu skądś:

// I am returning a helper somewhere 
public Helper<T> GetMethod<T>() 
{ 
    var product = GetProduct(1); 
    return new Helper<MyClass>(x => x.Delete(product)); 
} 

// some other class decides, when to execute the helper 
// Invoker already exists and is responsible for executing the method 
// that is the main reason I don't just comile and execute my Expression 
public bool ExecuteMethod<T>(Helper<T> helper) 
{ 
    var instance = new MyClass(); 
    var Invoker = new Invoker(helper.Type, helper.Method, helper.ArgTypes, helper.ArgValues); 
    return (bool)Invoker.Invoke(instance); 
} 

Punktem, w którym utknąłem, jest sposób wyodrębnienia argumentów z samego wyrażenia.

Znalazłem ten sposób

((ConstantExpression)((MemberExpression)body.Arguments[0]).Expression).Value 

który wydaje się być typu obiektu z polem „produktu”, ale uważam, że musi być prostsze rozwiązanie.

Wszelkie sugestie.

Aktualizacja

Wystarczy, aby wyjaśnić, że mój kod zmodyfikowany zgodnie z tym co chcę achive. W mojej prawdziwej aplikacji słowo mam już klasę, która robi to samo, ale bez drzewa wyrażenie:

var helper = new Helper(typeof(MyClass), "Delete", 
    new Type[] { typeof(Product) }, new object[] {product})); 

Głównym powodem mojego Helper<T> ma mieć kompilacji sprawdzanie, czy podpis jest poprawny sposób.

Aktualizacja 2

To jest moja obecna implementacja, czy istnieje lepszy sposób acces wartości, bez używania refleksji?

public Helper(Expression<Func<T, TResult>> expression) 
{ 
    var body = (System.Linq.Expressions.MethodCallExpression)expression.Body; 

    this.Type = typeof(T); 
    this.Method = body.Method.Name; 
    this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray(); 

    var values = new List<object>(); 
    foreach(var arg in body.Arguments) 
    { 
     values.Add(
      (((ConstantExpression)exp.Expression).Value).GetType() 
       .GetField(exp.Member.Name) 
       .GetValue(((ConstantExpression)exp.Expression).Value); 
     ); 
    } 
    this.ArgValues = values.ToArray(); 
} 
+5

jest jakiś powód, dla którego nie można po prostu skompilować wyrażenie i wykonać to? A może to po prostu błądzić, aby nauczyć się drzew ekspresji? –

+1

@ rbev +1 bicie mnie do tego. – Aron

+0

Heck, dlaczego nie po prostu wywołać bezpośrednio 'Delete (produkt)'? – Aron

Odpowiedz

13

Ta metoda działa całkiem dobrze. Zwraca typy argumentów i wartości za wyrażenie>

private static KeyValuePair<Type, object>[] ResolveArgs<T>(Expression<Func<T, object>> expression) 
    { 
     var body = (System.Linq.Expressions.MethodCallExpression)expression.Body; 
     var values = new List<KeyValuePair<Type, object>>(); 

     foreach (var argument in body.Arguments) 
     { 
      var exp = ResolveMemberExpression(argument); 
      var type = argument.Type; 

      var value = GetValue(exp); 

      values.Add(new KeyValuePair<Type, object>(type, value)); 
     } 

     return values.ToArray(); 
    } 

    public static MemberExpression ResolveMemberExpression(Expression expression) 
    { 

     if (expression is MemberExpression) 
     { 
      return (MemberExpression)expression; 
     } 
     else if (expression is UnaryExpression) 
     { 
      // if casting is involved, Expression is not x => x.FieldName but x => Convert(x.Fieldname) 
      return (MemberExpression)((UnaryExpression)expression).Operand; 
     } 
     else 
     { 
      throw new NotSupportedException(expression.ToString()); 
     } 
    } 

    private static object GetValue(MemberExpression exp) 
    { 
     // expression is ConstantExpression or FieldExpression 
     if (exp.Expression is ConstantExpression) 
     { 
      return (((ConstantExpression)exp.Expression).Value) 
        .GetType() 
        .GetField(exp.Member.Name) 
        .GetValue(((ConstantExpression)exp.Expression).Value);  
     } 
     else if (exp.Expression is MemberExpression) 
     { 
      return GetValue((MemberExpression)exp.Expression); 
     } 
     else 
     { 
      throw new NotImplementedException(); 
     } 
    } 
+0

Chyba masz na myśli (dodany brakujący argument type): 'KeyValuePair [] ResolveArgs (wyrażenie > wyrażenie)' –

+0

@PatrickKoorevaar Masz rację. W moim kodzie metoda jest wewnątrz klasy ogólnej z argumentem typu, więc tę część przeoczyłem. –

0

Oto przykład utworzenia delegata za pomocą lambda. Instancja obiektu jest enkapsulowana do delegata przy użyciu funkcji C# o nazwie zamknięcie.

MyClass instance = new MyClass(); 
    //This following line cannot be changed to var declaration 
    //since C# can't infer the type. 
Func<Product, bool> deleteDelegate = p => instance.Delete(p); 
Product product = new Product(); 
bool deleted = deleteDelegate(product); 

Alternatywnie próbujesz stworzyć pomocnika, który automagicznie Currys.

public class Helper<T> 
    where T : new() 
{ 
    public TResult Execute<TResult>(Func<T, TResult> methodLambda) 
    { 
     var instance = new T(); 
     return methodLamda(instance); 
    } 
} 

public void Main() 
{ 
    var helper = new Helper<MyClass>(); 
    var product = new Product(); 
    helper.Execute(x => x.Delete(product)); 
} 

Jednak muszę powiedzieć, że ten problem wygląda podejrzanie utworzenia klasy pomocnika do obsługi żywotność proxy WCF .... Wiesz ... po prostu powiedzieć ... w którym to przypadku ISN W jaki sposób chciałbym się do tego przyzwyczaić ... tylko dlatego, że takie podejście przenosi specyficzny kod WCF do twojej domeny.

+0

To zupełnie nie to, czego nie chcę. –

6

można skompilować wyrażenia argumentów, a następnie wywołać ją obliczyć wartość:

var values = new List<object>(); 
foreach(var arg in body.Arguments) 
{ 
    var value = Expression.Lambda(argument).Compile().DynamicInvoke(); 
    values.Add(value); 
} 
this.ArgValues = values.ToArray(); 
+0

Nie próbowałem jeszcze, ale wygląda obiecująco. Dzięki. –

+1

argumenty powinny być argami zadeklarowanymi w foreach. – swestner

+0

W [tym] (https://blogs.msdn.microsoft.com/csharpfaq/2010/03/11/how-can-i-get-objects-and-property-values- from-expression-trees/) artice wspomniał, że kompilacja nie jest szybką operacją. –

Powiązane problemy