2011-01-04 5 views
14

Chciałbym móc wygenerować skompilowane wyrażenie, aby ustawić właściwość, biorąc pod uwagę wyrażenie lambda, które zapewnia metoda "get" dla właściwości.Utwórz akcję, aby uzyskać właściwość, gdy otrzymam wyrażenie LINQ dla "get"

Oto co szukam:

public Action<int> CreateSetter<T>(Expression<Func<T, int>> getter) 
{ 
    // returns a compiled action using the details of the getter expression tree, or null 
    // if the write property is not defined. 
} 

Wciąż próbuję zrozumieć różne rodzaje klas wypowiedzi, więc jeśli można wskazać mi w dobrym kierunku, że byłoby świetnie.

+1

Nie masz na myśli "Akcji "? Moglibyśmy również wprowadzić parametr "int" jako typ. – Ani

+0

Ani, tak masz rację - miałem na myśli Action i int będzie oczywiście również parametrem generycznym. – Alex

Odpowiedz

12

Korzystanie @ odpowiedź Ani jako punkt wyjścia, można użyć następujących wygenerować skompilowanego wyrażenia.

[TestMethod] 
public void CreateSetterFromGetter() 
{ 
    Action<Person, int> ageSetter = InitializeSet((Person p) => p.Age); 
    Action<Person, string> nameSetter = InitializeSet((Person p) => p.Name); 

    Person p1 = new Person(); 
    ageSetter(p1, 29); 
    nameSetter(p1, "John"); 

    Assert.IsTrue(p1.Name == "John"); 
    Assert.IsTrue(p1.Age == 29); 
} 

public class Person { public int Age { get; set; } public string Name { get; set; } } 

public static Action<TContainer, TProperty> InitializeSet<TContainer, TProperty>(Expression<Func<TContainer, TProperty>> getter) 
{ 
    PropertyInfo propertyInfo = (getter.Body as MemberExpression).Member as PropertyInfo; 

    ParameterExpression instance = Expression.Parameter(typeof(TContainer), "instance"); 
    ParameterExpression parameter = Expression.Parameter(typeof(TProperty), "param"); 

    return Expression.Lambda<Action<TContainer, TProperty>>(
     Expression.Call(instance, propertyInfo.GetSetMethod(), parameter), 
     new ParameterExpression[] { instance, parameter }).Compile(); 
} 

Powinieneś buforować kompilowane wyrażenie, aby było przydatne dla wielu zastosowań.

+0

W pełni prawdziwe, ale nie jestem pewien, czy wyrażenie to najlepsza trasa tutaj. Ale przykład jest dobry (dla ilustracji). –

+0

+1 Niezły, Adam. – Ani

+0

Zaznaczam to jako odpowiedź, ponieważ używa klas Expression jako rozwiązania, którego właśnie szukałem. Dzięki Adam – Alex

7

Można oczywiście przejść drzewo ekspresji, a następnie użyć Delegate.CreateDelegate, aby utworzyć odpowiednie Action<,>. Jest to dość proste, z wyjątkiem wszystkich walidacji kontrole (jestem pewien, czy mam wszystko pokryte):

nie jestem ekspertem wyrażenie drzewa, ale nie myśleć budowania wypowiedzi -tree, a następnie wywołanie Compile jest możliwe tutaj, ponieważ drzewa-wyrażenia nie mogą zawierać instrukcji przypisania, o ile wiem. (EDIT: Najwyraźniej zostały one dodane w .NET 4. Jest to trudna do znalezienia funkcja, ponieważ kompilator C# nie wydaje się być w stanie zbudować ich z lambdas).

public static Action<TContaining, TProperty> 
    CreateSetter<TContaining, TProperty> 
    (Expression<Func<TContaining, TProperty>> getter) 
{ 
    if (getter == null) 
     throw new ArgumentNullException("getter"); 

    var memberEx = getter.Body as MemberExpression; 

    if (memberEx == null) 
     throw new ArgumentException("Body is not a member-expression."); 

    var property = memberEx.Member as PropertyInfo; 

    if (property == null) 
     throw new ArgumentException("Member is not a property."); 

    if(!property.CanWrite) 
     throw new ArgumentException("Property is not writable."); 

    return (Action<TContaining, TProperty>) 
      Delegate.CreateDelegate(typeof(Action<TContaining, TProperty>), 
            property.GetSetMethod()); 
} 

Wykorzystanie:

public class Person { public int Age { get; set; } } 

... 

static void Main(string[] args) 
{ 
    var setter = CreateSetter((Person p) => p.Age); 
    var person = new Person(); 
    setter(person, 25); 

    Console.WriteLine(person.Age); // 25  
} 

Należy pamiętać, że to tworzy otwarty instancji delegata, co oznacza, że ​​nie jest związana z żadnym konkretnym wystąpieniem TContaining. Można go łatwo zmodyfikować, aby był związany z instancją specyficzną dla instancji; musisz również podać metodę TContaining, a następnie użyć innego przeciążenia: Delegate.CreateDelegate. Podpis metody będzie wtedy wyglądać tak:

public static Action<TProperty> CreateSetter<TContaining, TProperty> 
     (Expression<Func<TContaining, TProperty>> getter, TContaining obj) 
+2

Nie jestem pewien, ale myślę, że dodali zadania w .net 4. Ale seter powinien już mieć poprawny podpis, więc nie jest to konieczne. – CodesInChaos

+2

Tak, zadanie jest w wersji 4.0 - ale naprawdę nie jest ono potrzebne; Delegate.CreateDelegate jest idealny. +1 –

+0

@CodeInChaos, @Marc Gravell: Dzięki, to była dla mnie nowość. – Ani

4

Wskaźniki tylko obawiam się (nie jestem przy komputerze) - ale;

  • lambda za .Body najprawdopodobniej będzie MemberExpression
  • zrobić bezpiecznej obsady (as etc) i dostęp do .Member
  • ponieważ wierzę tę właściwość, powinno to być PropertyInfo, więc test/cast etc
  • ze PropertyInfo, zadzwoń GetSetMethod(), aby uzyskać odpowiedni MethodInfo
  • użytkowania Delegate.CreateDelegate aby ta jako delegat (przekazując typ działania)
  • finał ly, oddanych Delegat powrócił na spodziewać delegata typu
+1

Wielkie umysły .... – Ani

Powiązane problemy