2010-05-12 2 views
31

J'ai créé des méthodes pour convertir un lambda de propriété à un délégué:Création d'un délégué setter de propriété

public static Delegate MakeGetter<T>(Expression<Func<T>> propertyLambda) 
{ 
    var result = Expression.Lambda(propertyLambda.Body).Compile(); 
    return result; 
} 

public static Delegate MakeSetter<T>(Expression<Action<T>> propertyLambda) 
{ 
    var result = Expression.Lambda(propertyLambda.Body).Compile(); 
    return result; 
} 

Ces travaux:

Delegate getter = MakeGetter(() => SomeClass.SomeProperty); 
object o = getter.DynamicInvoke(); 

Delegate getter = MakeGetter(() => someObject.SomeProperty); 
object o = getter.DynamicInvoke(); 

mais ceux-ci ne compilera pas:

Delegate setter = MakeSetter(() => SomeClass.SomeProperty); 
setter.DynamicInvoke(new object[]{propValue}); 

Delegate setter = MakeSetter(() => someObject.SomeProperty); 
setter.DynamicInvoke(new object[]{propValue}); 

Les lignes MakeSetter échouent avec "Les arguments de type ne peuvent pas être déduits de l'utilisation. Essayez de spécifier les arguments de type e explicitement. "

Est ce que j'essaye de faire possible? Merci d'avance.

Répondre

4

Action<T> représente un délégué qui prend un paramètre de type T et ne renvoie rien. Les expressions lambda que vous fournissez à MakeSetter représentent des délégués qui ne prennent aucun paramètre et renvoient les codes SomeClass.SomeProperty ou someObject.SomeProperty. Les messages d'erreur que vous obtenez sont dus au fait que le compilateur ne peut pas déduire les types des expressions lambda que vous transmettez dans la méthode MakeSetter car ce que vous avez passé et ce que la méthode attend ne sont pas dans synchroniser.

+0

Qu'est-ce que lambda ferait fonctionner? –

4

Votre MakeSetter attend un Action<T> et vous lui transmettez un Func<T> (() => someObject.SomeProperty). Effectuez les actions suivantes:

Delegate setter = MakeSetter((prop) => {someObject.SomeProperty = prop;}); 
setter.DynamicInvoke(new object[]{propValue}); 

EDIT Ne ressemble pas à vous pouvez convert statement lambdas into expressions. Ceci est un peu d'une manière détournée de le faire sans expression - droit aux délégués:

class Test2 { 
    delegate void Setter<T>(T value); 

    public static void Test() { 
     var someObject = new SomeObject(); 
     Setter<string> setter = (v) => { t.SomeProperty = v; }; 
     setter.DynamicInvoke(new object[]{propValue}); 
    } 
} 
+0

Je reçois "Une expression lambda avec un corps d'instruction ne peut pas être convertie en arbre d'expression". –

+0

devrez peut-être créer l'expression manuellement. Donne-moi une seconde. –

47

L'API Expression prend en charge ce dans .NET 4.0, mais malheureusement le compilateur C# ne pas ajouter de bonbons supplémentaire pour soutenir. Mais la bonne nouvelle est que vous pouvez facilement prendre une expression "get" (que le compilateur C# peut écrire) et la réécrire comme une expression "set".

Et encore mieux; si vous n'avez pas .NET 4.0, il reste au moins deux autres façons d'effectuer un "set" via une expression écrite en "get".

Ici, ils sont tous, pour info:

using System; 
using System.Linq.Expressions; 
using System.Reflection; 
class Foo { 
    public string Bar { get; set; } 
    static void Main() { 
     // take a "get" from C# 
     Expression<Func<Foo, string>> get = foo => foo.Bar; 

     // re-write in .NET 4.0 as a "set" 
     var member = (MemberExpression)get.Body; 
     var param = Expression.Parameter(typeof(string), "value"); 
     var set = Expression.Lambda<Action<Foo, string>>(
      Expression.Assign(member, param), get.Parameters[0], param); 

     // compile it 
     var action = set.Compile(); 
     var inst = new Foo(); 
     action(inst, "abc"); 
     Console.WriteLine(inst.Bar); // show it working 

     //==== reflection 
     MethodInfo setMethod = ((PropertyInfo)member.Member).GetSetMethod(); 
     setMethod.Invoke(inst, new object[] { "def" }); 
     Console.WriteLine(inst.Bar); // show it working 

     //==== Delegate.CreateDelegate 
     action = (Action<Foo, string>) 
      Delegate.CreateDelegate(typeof(Action<Foo, string>), setMethod); 
     action(inst, "ghi"); 
     Console.WriteLine(inst.Bar); // show it working 
    } 
} 
+2

Peut-être une question idiote mais pourquoi le '/' au lieu de '*' pour les italiques? –

+1

@Camilo Vieilles habitudes BBS meurent dur –

+0

Cela m'a été très utile! Une différence subtile lors de l'utilisation d'Invoke sur MethodInfo est que le type ne doit pas correspondre exactement. Vous pouvez donc créer une méthode generate MakeSetter et lui donner une expression >. Les deux autres moyens échouent à l'exécution si vous essayez cela. –

8

Selon mes commentaires - parce que les liens vont mort - j'ai posté le code complet en réponse à la question. OUI il est possible de faire ce que l'OP demande. et voici un joli petit bijou de Nick qui le démontre. Nick crédite cette page et une autre page pour sa solution complète avec des mesures de performance. Je fournis ci-dessous au lieu de just a link.

// returns property getter 
public static Func<TObject, TProperty> GetPropGetter<TObject, TProperty>(string propertyName) 
{ 
    ParameterExpression paramExpression = Expression.Parameter(typeof(TObject), "value"); 

    Expression propertyGetterExpression = Expression.Property(paramExpression, propertyName); 

    Func<TObject, TProperty> result = 
     Expression.Lambda<Func<TObject, TProperty>>(propertyGetterExpression, paramExpression).Compile(); 

    return result; 
} 

// returns property setter: 
public static Action<TObject, TProperty> GetPropSetter<TObject, TProperty>(string propertyName) 
{    
    ParameterExpression paramExpression = Expression.Parameter(typeof(TObject)); 

    ParameterExpression paramExpression2 = Expression.Parameter(typeof(TProperty), propertyName); 

    MemberExpression propertyGetterExpression = Expression.Property(paramExpression, propertyName); 

    Action<TObject, TProperty> result = Expression.Lambda<Action<TObject, TProperty>> 
    (
     Expression.Assign(propertyGetterExpression, paramExpression2), paramExpression, paramExpression2 
    ).Compile(); 

    return result; 
} 
+0

C'est bien. La seule solution non-stupide-délégué sur SO. Tu es mon héros ;-) Je savais qu'il devait être possible de le faire purement avec des expressions mais j'ai supervisé le 'Assign'. – t3chb0t

Questions connexes