2010-05-15 7 views
7

Es ist relativ einfach, eine Lambda-Funktion zu erstellen, die den Wert einer Eigenschaft von einem Objekt zurück, sogar einschließlich tiefen Eigenschaften ...von Funktionsausdruck

Func<Category, string> getCategoryName = new Func<Category, string>(c => c.Name); 

und dies kann als genannt werden folgt ...

string categoryName = getCategoryName(this.category); 

Aber, da nur die resultierende Funktion oben (oder der Ausdruck ursprünglich verwendet, um die Funktion zu erstellen), eine einfache Möglichkeit, jemand schaffen kann schaffen die gegnerische Aktion ...

Action<Category, string> setCategoryName = new Action<Category, string>((c, s) => c.Name = s);

... das wird die gleiche Eigenschaft Wert gesetzt werden wie folgt aktivieren?

setCategoryName(this.category, ""); 

Bitte beachte, dass ich nach einem Weg suche die Aktion programmatisch von der Funktion oder Ausdruck zu schaffen - ich hoffe, dass ich gezeigt habe, dass ich schon wissen, wie es manuell zu erstellen.

Ich bin offen für Antworten, die sowohl in .net 3.5 und 4.0 funktionieren.

Danke.

UPDATE:

Vielleicht ich in meiner Frage bin nicht klar zu sein, so lassen Sie mich versuchen und zeigen deutlicher, was ich zu tun versuche.

Ich habe folgendes Verfahren (das habe ich für die Zwecke dieser Frage erstellt) ...

void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) { 

    Func<TObject, TValue> getValue = expression.Compile(); 
    TValue stuff = getValue(obj); 

    Expression<Action<TObject, TValue>> assignmentExpression = (o, v) => Expression<TObject>.Assign(expression, Expression.Constant(v, typeof(TValue))); 
    Action<TObject, TValue> setValue = assignmentExpression.Compile(); 

    setValue(obj, stuff); 

} 

Was ich suche ist, wie ich die „assignmentExpression“ im Code erstellen, so dass Ich kann es in setValue kompilieren? Ich denke, es ist mit Expression.Assign verwandt, aber ich kann einfach nicht die richtige Kombination von Parametern erarbeiten, um den Code zu vervollständigen.

Das schließliche Ergebnis ist in der Lage sein

Category category = *<get object from somewhere>*; 
this.DoLambdaStuff(category, c => c.Name); 

anrufen und dies wiederum wird einen Getter und Setter für die Eigenschaft „Name“ des Kategorie-Objekt erstellen.

Die obige Version kompiliert, aber wenn ich setValue() aufrufen, führt dies zu einer ArgumentException mit "Expression muss beschreibbar sein".

Nochmals vielen Dank.

+0

Ich verstehe nicht wirklich, was Sie meinen, indem sie es automatisch auf manuell im Gegensatz zu tun. Wenn Sie jedoch Eigenschaften festlegen und entscheiden möchten, welche Eigenschaft zur Laufzeit festgelegt werden soll, sollten Sie reflection verwenden. Darüber hinaus können Sie die Ausdrucksbaumstruktur zum Erstellen von Laufzeit-Lambda-Ausdrücken verwenden. – Henri

Antwort

0

Dies sollte mit expression trees möglich sein, die aus Lambda-Ausdrücken erstellt, geändert und dann in einen Delegaten kompiliert werden kann.

+5

So habe ich gesucht, ich habe nur auf ein Beispiel gehofft! –

3

Ok, der Code ich suche geht so etwas ...

ParameterExpression objectParameterExpression = Expression.Parameter(
    typeof(TObject)), 
    valueParameterExpression = Expression.Parameter(typeof(TValue) 
); 
Expression<Action<TObject, TValue>> setValueExpression = Expression.Lambda<Action<TObject, TValue>>(
    Expression.Block(
    Expression.Assign(
     Expression.Property(
     objectParameterExpression, 
     ((MemberExpression) expression.Body).Member.Name 
    ), 
     valueParameterExpression 
    ) 
), 
    objectParameterExpression, 
    valueParameterExpression 
); 
Action<TObject, TValue> setValue = setValueExpression.Compile(); 

Dieser Code funktioniert, aber nur für flache Eigenschaften (dh die Eigenschaften des unmittelbaren Objekt), aber funktioniert nicht für tiefe Eigenschaften - obwohl die Getter-Funktion für tiefe Eigenschaften funktionieren kann. Es wäre interessant zu wissen, ob jemand mir helfen kann, dies zu modifizieren, um mit tiefen Eigenschaften zu arbeiten, aber ich werde das als eine separate Frage aufwerfen.

+2

Dieser Code funktioniert nur in .NET 4, da es die verbesserte Unterstützung für in .NET 4 eingeführte Ausdrucksbäume erfordert. –

2

Wie Martin oben erwähnt, "tiefe" Eigenschaften brechen seine Lösung. Der Grund, warum es nicht funktioniert, ist dieser Ausdruck:

Expression.Property(
    objectParameterExpression 
, ((MemberExpression)expression.Body).Member.Name 
) 

Der Grund dafür ist nicht sofort offensichtlich: die abgeleitete Klasse seine eigene Eigenschaft Getter erklärt, die auf die Basisklasse leitet, aber nicht definiert, eine entsprechende Setter .

Um dieses Problem zu umgehen, müssen Sie die Eigenschaft durch Reflektion suchen, nach dem Deklarationstyp suchen und dann die entsprechende Eigenschaft vom Deklarierer abrufen. Im Gegensatz zu der Eigenschaft, die Sie für die abgeleitete Klasse erhalten, besitzt die Eigenschaft des Deklarators einen Getter und einen Setter und ist daher zuweisbar. (Dies setzt voraus, dass ein Setter der Eigenschaft überhaupt deklariert wird: Wenn der Alleinsteller keinen Setter bereitstellt, könnte ihn niemand anders bereitstellen.)

Hier ist eine skelettierte Implementierung, die dieses Problem anspricht. Ersetzen Sie den obigen Anruf durch den Aufruf GetPropertyOrField von Expression.Property und Martins Lösung wird funktionieren.

private static MemberExpression GetPropertyOrField(Expression baseExpr, string name) { 
    if (baseExpr == null) { 
     throw new ArgumentNullException("baseExpr"); 
    } 
    if (string.IsNullOrWhiteSpace(name)) { 
     throw new ArgumentException("name"); 
    } 
    var type = baseExpr.Type; 
    var properties = type 
     .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
     .Where(p => p.Name.Equals(name)) 
     .ToArray(); 
    if (properties.Length == 1) { 
     var res = properties[0]; 
     if (res.DeclaringType != type) { 
      // Here is the core of the fix: 
      var tmp = res 
       .DeclaringType 
       .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
       .Where(p => p.Name.Equals(name)) 
       .ToArray(); 
      if (tmp.Length == 1) { 
       return Expression.Property(baseExpr, tmp[0]); 
      } 
     } 
     return Expression.Property(baseExpr, res); 
    } 
    if (properties.Length != 0) { 
     throw new NotSupportedException(name); 
    } 
    var fields = type 
     .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 
     .Where(p => p.Name.Equals(name)) 
     .ToArray(); 
    if (fields.Length == 1) { 
     return Expression.Field(baseExpr, fields[0]); 
    } 
    if (fields.Length != 0) { 
     throw new NotSupportedException(name); 
    } 
    throw new ArgumentException(
     string.Format(
      "Type [{0}] does not define property/field called [{1}]" 
     , type 
     , name 
     ) 
    ); 
} 
5
void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) { 

    Func<TObject, TValue> getValue = expression.Compile(); 
    TValue stuff = getValue(obj); 

    var p = Expression.Parameter(typeof(TValue), "v"); 
    Expression<Action<TObject, TValue>> assignmentExpression = 
     Expression.Lambda<Action<TObject, TValue>>(Expression.Assign(expression.Body, p), expression.Parameters[0], p); 

    Action<TObject, TValue> setValue = assignmentExpression.Compile(); 

    setValue(obj, stuff); 
} 
+0

Können Sie erklären, was Ihr Code macht? – Math

+0

+1, so einfach. – nawfal

+1

+1. Mit diesem Code kann ich auch Werte für private Felder und verschachtelte private Eigenschaften festlegen. Der Typ des Felds/der Eigenschaft könnte auch ein 'struct' sein und es funktioniert einfach. Würde wirklich eine Erklärung dafür lieben, wie das funktioniert. –

Verwandte Themen