Abrufen des Eigenschaftsnamens aus dem Lambda-Ausdruck

513

Gibt es eine bessere Möglichkeit, den Eigenschaftsnamen zu erhalten, wenn er über einen Lambda-Ausdruck übergeben wird? Hier ist was ich derzeit habe.

z.B.

GetSortingInfo<User>(u => u.UserId);

Es funktionierte, indem es nur dann als Mitgliedsausdruck umgewandelt wurde, wenn die Eigenschaft eine Zeichenfolge war. Da nicht alle Eigenschaften Zeichenfolgen sind, musste ich ein Objekt verwenden, aber dann würde es einen unären Ausdruck für diese zurückgeben.

public static RouteValueDictionary GetInfo<T>(this HtmlHelper html, 
    Expression<Func<T, object>> action) where T : class
{
    var expression = GetMemberInfo(action);
    string name = expression.Member.Name;

    return GetInfo(html, name);
}

private static MemberExpression GetMemberInfo(Expression method)
{
    LambdaExpression lambda = method as LambdaExpression;
    if (lambda == null)
        throw new ArgumentNullException("method");

    MemberExpression memberExpr = null;

    if (lambda.Body.NodeType == ExpressionType.Convert)
    {
        memberExpr = 
            ((UnaryExpression)lambda.Body).Operand as MemberExpression;
    }
    else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
    {
        memberExpr = lambda.Body as MemberExpression;
    }

    if (memberExpr == null)
        throw new ArgumentException("method");

    return memberExpr;
}
Schotime
quelle
Besser als in schönerem Code? Das glaube ich nicht. Die Typüberprüfung erstreckt sich nur auf den Gesamtausdruck, sodass Sie die Überprüfungen, die Sie zur Laufzeit durchführen, wirklich benötigen. :(
MichaelGG
Ja ... ich habe mich nur gefragt, ob es einen besseren Weg gibt, da es sich für mich ein wenig hackig anfühlte. Aber wenn das so ist, dann cool. Vielen Dank.
Schotime
Ich habe Ihren Kommentar aktualisiert. Aber wenn Sie ein Lambda verwenden, um eine Zeichenfolge zu erhalten, damit Sie dynamisches LINQ verwenden können, scheint es mir, als würden Sie Dinge rückwärts tun ... Wenn Sie ein Lambda verwenden, verwenden Sie ein Lambda ;-p Sie müssen nicht die gesamte Abfrage in einem Schritt ausführen - Sie könnten "Regular / Lambda" OrderBy, "Dynamic LINQ / String" Where usw. verwenden
Marc Gravell
1
Mögliches Duplikat von get-property-name-and-type-using-lambda-expression
nawfal
4
Ein Hinweis für alle: Verwenden Sie den MemberExpressionhier aufgeführten Ansatz nur, um den Namen des Mitglieds abzurufen , nicht um den tatsächlichen Namen MemberInfoselbst MemberInfoabzurufen , da in bestimmten "dervied: base" -Szenarien nicht garantiert wird, dass der zurückgegebene Typ vom reflektierten Typ ist. Siehe Lambda-Ausdruck-nicht-Rückgabe-erwartet-Mitgliedinfo . Hat mich einmal gestolpert. Darunter leidet auch die akzeptierte Antwort.
Nawfal

Antworten:

350

Ich habe kürzlich eine sehr ähnliche Aktion durchgeführt, um eine typsichere OnPropertyChanged-Methode zu erstellen.

Hier ist eine Methode, die das PropertyInfo-Objekt für den Ausdruck zurückgibt. Es wird eine Ausnahme ausgelöst, wenn der Ausdruck keine Eigenschaft ist.

public PropertyInfo GetPropertyInfo<TSource, TProperty>(
    TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda)
{
    Type type = typeof(TSource);

    MemberExpression member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));

    PropertyInfo propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));

    if (type != propInfo.ReflectedType &&
        !type.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

Der sourceParameter wird verwendet, damit der Compiler beim Methodenaufruf eine Typinferenz durchführen kann. Sie können Folgendes tun

var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
Cameron MacFarland
quelle
6
Warum ist dort die letzte Überprüfung bezüglich TSource? Das Lambda ist stark getippt, daher halte ich es nicht für notwendig.
HappyNomad
16
Ab 2012 funktioniert die Typinferenz auch ohne den Quellparameter einwandfrei.
HappyNomad
4
@HappyNomad Stellen Sie sich ein Objekt vor, das als Mitglied eine Instanz eines dritten Typs hat. u => u.OtherType.OtherTypesPropertywürde einen solchen Fall erstellen, auf den die letzte Anweisung prüft.
Joshperry
5
Die letzte if-Anweisung sollte lauten: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))um auch Schnittstellen zu berücksichtigen.
Graham King
8
@GrayKing wäre das nicht dasselbe wie nur if(!propInfo.ReflectedType.IsAssignableFrom(type))?
Connell
192

Ich habe eine andere Möglichkeit gefunden, die Quelle und die Eigenschaft stark zu tippen und die Eingabe für das Lambda explizit abzuleiten. Ich bin mir nicht sicher, ob dies die richtige Terminologie ist, aber hier ist das Ergebnis.

public static RouteValueDictionary GetInfo<T,P>(this HtmlHelper html, Expression<Func<T, P>> action) where T : class
{
    var expression = (MemberExpression)action.Body;
    string name = expression.Member.Name;

    return GetInfo(html, name);
}

Und dann nenne es so.

GetInfo((User u) => u.UserId);

und voila es funktioniert.
Vielen Dank an alle.

Schotime
quelle
4
Diese Lösung sollte ein wenig aktualisiert werden. Bitte überprüfen Sie den folgenden Artikel - hier ist ein Link
Pavel Cermak
1
Dies ist nur eine Option, wenn Sie ASP.Net MVC ausführen und nur für die UI-Ebene (HtmlHelper).
Marc
3
ab c # 6.0 können Sie verwendenGetInfo(nameof(u.UserId))
Vladislav
1
Im Netzkern musste ich folgendes verwenden:var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk
146

Ich habe mit der gleichen Sache herumgespielt und das aufgearbeitet. Es ist nicht vollständig getestet, scheint aber das Problem mit Werttypen zu behandeln (das Problem mit dem unären Ausdruck, auf das Sie gestoßen sind).

public static string GetName(Expression<Func<object>> exp)
{
    MemberExpression body = exp.Body as MemberExpression;

    if (body == null) {
       UnaryExpression ubody = (UnaryExpression)exp.Body;
       body = ubody.Operand as MemberExpression;
    }

    return body.Member.Name;
}
M Thelen
quelle
2
habe dies kürzlich versucht (aus einer anderen Frage ), herausgefunden, dass es keine Untereigenschaften behandelt: o => o.Thing1.Thing2würde zurückkehren Thing2, nicht Thing1.Thing2, was falsch ist, wenn Sie versuchen, es in EntityFramework-Includes zu verwenden
drzaus
1
AKA (field.Body ist UnaryExpression? ((UnaryExpression) field.Body) .Operand: field.Body) als MemberExpression
51
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}

Dies behandelt Element- und unäre Ausdrücke. Der Unterschied besteht darin, dass Sie ein erhalten, UnaryExpressionwenn Ihr Ausdruck einen Werttyp darstellt, während Sie ein erhalten, MemberExpressionwenn Ihr Ausdruck einen Referenztyp darstellt. Alles kann in ein Objekt umgewandelt werden, aber Werttypen müssen eingerahmt werden. Aus diesem Grund ist der UnaryExpression vorhanden. Referenz.

Aus Gründen der Lesbarkeit (@Jowen) ist hier ein erweitertes Äquivalent:

public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    if (object.Equals(Field, null))
    {
        throw new NullReferenceException("Field is required");
    }

    MemberExpression expr = null;

    if (Field.Body is MemberExpression)
    {
        expr = (MemberExpression)Field.Body;
    }
    else if (Field.Body is UnaryExpression)
    {
        expr = (MemberExpression)((UnaryExpression)Field.Body).Operand;
    }
    else
    {
        const string Format = "Expression '{0}' not supported.";
        string message = string.Format(Format, Field);

        throw new ArgumentException(message, "Field");
    }

    return expr.Member.Name;
}
Paul Fleming
quelle
@flem, ich lasse <TField> aus Gründen der Lesbarkeit weg, gibt es ein Problem. LambdaExpressions.GetName <Basket> (m => m.Quantity)
Soren
1
@soren Ich bin sicher, dass jemand, der besser eingestellt ist als ich, vorschlagen könnte, dass Sie Ihren Code für das Potenzial unnötigen Ein- und Auspackens öffnen, wenn Sie Ausdrücke von Werttypen übergeben, aber weil der Ausdruck in dieser Methode niemals kompiliert und ausgewertet wird. Es ist wahrscheinlich kein Problem.
Paul Fleming
30

Mit C # 7 Mustervergleich:

public static string GetMemberName<T>(this Expression<T> expression)
{
    switch (expression.Body)
    {
        case MemberExpression m:
            return m.Member.Name;
        case UnaryExpression u when u.Operand is MemberExpression m:
            return m.Member.Name;
        default:
            throw new NotImplementedException(expression.GetType().ToString());
    }
}

Beispiel:

public static RouteValueDictionary GetInfo<T>(this HtmlHelper html, 
    Expression<Func<T, object>> action) where T : class
{
    var name = action.GetMemberName();
    return GetInfo(html, name);
}

[Update] C # 8-Mustervergleich:

public static string GetMemberName<T>(this Expression<T> expression) =>
    expression.Body switch
    {
        MemberExpression m =>
            m.Member.Name,
        UnaryExpression u when u.Operand is MemberExpression m =>
            m.Member.Name,
        _ =>
            throw new    NotImplementedException(expression.GetType().ToString())
    };
Akhansari
quelle
20

Dies ist eine allgemeine Implementierung, um den Zeichenfolgennamen der Felder / Eigenschaften / Indexer / Methoden / Erweiterungsmethoden / Delegaten von struct / class / interface / delegate / array abzurufen. Ich habe mit Kombinationen von statischen / Instanz- und nicht generischen / generischen Varianten getestet.

//involves recursion
public static string GetMemberName(this LambdaExpression memberSelector)
{
    Func<Expression, string> nameSelector = null;  //recursive func
    nameSelector = e => //or move the entire thing to a separate recursive method
    {
        switch (e.NodeType)
        {
            case ExpressionType.Parameter:
                return ((ParameterExpression)e).Name;
            case ExpressionType.MemberAccess:
                return ((MemberExpression)e).Member.Name;
            case ExpressionType.Call:
                return ((MethodCallExpression)e).Method.Name;
            case ExpressionType.Convert:
            case ExpressionType.ConvertChecked:
                return nameSelector(((UnaryExpression)e).Operand);
            case ExpressionType.Invoke:
                return nameSelector(((InvocationExpression)e).Expression);
            case ExpressionType.ArrayLength:
                return "Length";
            default:
                throw new Exception("not a proper member selector");
        }
    };

    return nameSelector(memberSelector.Body);
}

Dieses Ding kann auch in einer einfachen whileSchleife geschrieben werden:

//iteration based
public static string GetMemberName(this LambdaExpression memberSelector)
{
    var currentExpression = memberSelector.Body;

    while (true)
    {
        switch (currentExpression.NodeType)
        {
            case ExpressionType.Parameter:
                return ((ParameterExpression)currentExpression).Name;
            case ExpressionType.MemberAccess:
                return ((MemberExpression)currentExpression).Member.Name;
            case ExpressionType.Call:
                return ((MethodCallExpression)currentExpression).Method.Name;
            case ExpressionType.Convert:
            case ExpressionType.ConvertChecked:
                currentExpression = ((UnaryExpression)currentExpression).Operand;
                break;
            case ExpressionType.Invoke:
                currentExpression = ((InvocationExpression)currentExpression).Expression;
                break;
            case ExpressionType.ArrayLength:
                return "Length";
            default:
                throw new Exception("not a proper member selector");
        }
    }
}

Ich mag den rekursiven Ansatz, obwohl der zweite möglicherweise leichter zu lesen ist. Man kann es so nennen:

someExpr = x => x.Property.ExtensionMethod()[0]; //or
someExpr = x => Static.Method().Field; //or
someExpr = x => VoidMethod(); //or
someExpr = () => localVariable; //or
someExpr = x => x; //or
someExpr = x => (Type)x; //or
someExpr = () => Array[0].Delegate(null); //etc

string name = someExpr.GetMemberName();

um das letzte Mitglied zu drucken.

Hinweis:

  1. Bei verketteten Ausdrücken wie A.B.C"C" wird zurückgegeben.

  2. Dies funktioniert nicht mit consts, Array-Indexern oder enums (es ist unmöglich, alle Fälle abzudecken).

nawfal
quelle
19

Es gibt einen ArrayRandfall, wenn es um .Length geht. Während 'Länge' als Eigenschaft verfügbar gemacht wird, können Sie es in keiner der zuvor vorgeschlagenen Lösungen verwenden.

using Contract = System.Diagnostics.Contracts.Contract;
using Exprs = System.Linq.Expressions;

static string PropertyNameFromMemberExpr(Exprs.MemberExpression expr)
{
    return expr.Member.Name;
}

static string PropertyNameFromUnaryExpr(Exprs.UnaryExpression expr)
{
    if (expr.NodeType == Exprs.ExpressionType.ArrayLength)
        return "Length";

    var mem_expr = expr.Operand as Exprs.MemberExpression;

    return PropertyNameFromMemberExpr(mem_expr);
}

static string PropertyNameFromLambdaExpr(Exprs.LambdaExpression expr)
{
         if (expr.Body is Exprs.MemberExpression)   return PropertyNameFromMemberExpr(expr.Body as Exprs.MemberExpression);
    else if (expr.Body is Exprs.UnaryExpression)    return PropertyNameFromUnaryExpr(expr.Body as Exprs.UnaryExpression);

    throw new NotSupportedException();
}

public static string PropertyNameFromExpr<TProp>(Exprs.Expression<Func<TProp>> expr)
{
    Contract.Requires<ArgumentNullException>(expr != null);
    Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);

    return PropertyNameFromLambdaExpr(expr);
}

public static string PropertyNameFromExpr<T, TProp>(Exprs.Expression<Func<T, TProp>> expr)
{
    Contract.Requires<ArgumentNullException>(expr != null);
    Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);

    return PropertyNameFromLambdaExpr(expr);
}

Jetzt Beispielverwendung:

int[] someArray = new int[1];
Console.WriteLine(PropertyNameFromExpr( () => someArray.Length ));

Wenn PropertyNameFromUnaryExprnicht nach ArrayLength"someArray" gesucht wird, wird auf der Konsole gedruckt (der Compiler scheint als Optimierung auch in Debug, also im Sonderfall , direkten Zugriff auf das Feld " Backing Length" zu generieren ).

kornman00
quelle
16

Hier ist ein Update der von Cameron vorgeschlagenen Methode . Der erste Parameter ist nicht erforderlich.

public PropertyInfo GetPropertyInfo<TSource, TProperty>(
    Expression<Func<TSource, TProperty>> propertyLambda)
{
    Type type = typeof(TSource);

    MemberExpression member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));

    PropertyInfo propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));

    if (type != propInfo.ReflectedType &&
        !type.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(string.Format(
            "Expresion '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

Sie können Folgendes tun:

var propertyInfo = GetPropertyInfo<SomeType>(u => u.UserID);
var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID);

Erweiterungsmethoden:

public static PropertyInfo GetPropertyInfo<TSource, TProperty>(this TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
    return GetPropertyInfo(propertyLambda);
}

public static string NameOfProperty<TSource, TProperty>(this TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
    PropertyInfo prodInfo = GetPropertyInfo(propertyLambda);
    return prodInfo.Name;
}

Du kannst:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);
Adrian
quelle
Nein, er wird nicht uals Typ schließen, das kann er nicht, weil es keinen Typ gibt, auf den man schließen kann. Was Sie tun können, istGetPropertyInfo<SomeType>(u => u.UserID)
Lucas
14

Ich habe festgestellt, dass einige der vorgeschlagenen Antworten einen Drilldown in die MemberExpression/ UnaryExpressionnicht erfassten verschachtelten / Untereigenschaften durchführen.

ex) o => o.Thing1.Thing2kehrt Thing1eher zurück als Thing1.Thing2.

Diese Unterscheidung ist wichtig, wenn Sie versuchen, mit EntityFramework zu arbeiten DbSet.Include(...).

Ich habe festgestellt, dass nur das Parsen des Expression.ToString()zu funktionieren scheint, und vergleichsweise schnell. Ich verglich es mit der UnaryExpressionVersion und stieg sogar aus, ToStringum Member/UnaryExpressionzu sehen, ob das schneller war, aber der Unterschied war vernachlässigbar. Bitte korrigieren Sie mich, wenn dies eine schreckliche Idee ist.

Die Erweiterungsmethode

/// <summary>
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas.  Technique @via https://stackoverflow.com/a/16647343/1037948
/// </summary>
/// <remarks>Cheats and uses the tostring output -- Should consult performance differences</remarks>
/// <typeparam name="TModel">the model type to extract property names</typeparam>
/// <typeparam name="TValue">the value type of the expected property</typeparam>
/// <param name="propertySelector">expression that just selects a model property to be turned into a string</param>
/// <param name="delimiter">Expression toString delimiter to split from lambda param</param>
/// <param name="endTrim">Sometimes the Expression toString contains a method call, something like "Convert(x)", so we need to strip the closing part from the end</param>
/// <returns>indicated property name</returns>
public static string GetPropertyName<TModel, TValue>(this Expression<Func<TModel, TValue>> propertySelector, char delimiter = '.', char endTrim = ')') {

    var asString = propertySelector.ToString(); // gives you: "o => o.Whatever"
    var firstDelim = asString.IndexOf(delimiter); // make sure there is a beginning property indicator; the "." in "o.Whatever" -- this may not be necessary?

    return firstDelim < 0
        ? asString
        : asString.Substring(firstDelim+1).TrimEnd(endTrim);
}//--   fn  GetPropertyNameExtended

(Das Überprüfen des Trennzeichens ist möglicherweise sogar übertrieben.)

Demo (LinqPad)

Demonstrations- und Vergleichscode - https://gist.github.com/zaus/6992590

drzaus
quelle
1
+ 1 sehr interessant. Haben Sie diese Methode weiterhin in Ihrem eigenen Code verwendet? funktioniert es ok Haben Sie Randfälle entdeckt?
Benjamin Gale
Ich sehe deine Idee nicht. Wenn Sie sich an die Antwort halten, die Sie verlinkt haben o => o.Thing1.Thing2, kehren Thing1Sie nicht zurück, wie Sie sagen Thing2. Tatsächlich gibt Ihre Antwort so etwas zurück, wie es Thing1.Thing2vielleicht erwünscht ist oder nicht.
Nawfal
Funktioniert nicht mit dem Fall korman Vorsichtsmaßnahmen: stackoverflow.com/a/11006147/661933 . Immer besser, um Hacks zu vermeiden.
Nawfal
@nawfal # 1 - das ursprüngliche Problem ist, dass Sie nie wollen . Ich sagte, was den Wert von bedeutet , der der Punkt des Prädikats ist. Ich werde die Antwort aktualisieren, um diese Absicht widerzuspiegeln. Thing1.Thing2Thing1Thing2o.Thing1.Thing2
Drzaus
@drzaus Entschuldigung, ich verstehe dich immer noch nicht. Ich versuche wirklich zu verstehen. Warum würden Sie sagen, dass andere Antworten hier zurückkehren Thing1? Ich glaube nicht, dass es das überhaupt wiederholt.
Nawfal
6

Ich verwende eine Erweiterungsmethode für Projekte vor C # 6 und den Namen () für diejenigen, die auf C # 6 abzielen.

public static class MiscExtentions
{
    public static string NameOf<TModel, TProperty>(this object @object, Expression<Func<TModel, TProperty>> propertyExpression)
    {
        var expression = propertyExpression.Body as MemberExpression;
        if (expression == null)
        {
            throw new ArgumentException("Expression is not a property.");
        }

        return expression.Member.Name;
    }
}

Und ich nenne es wie:

public class MyClass 
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
    public int[] Property3 { get; set; }
    public Subclass Property4 { get; set; }
    public Subclass[] Property5 { get; set; }
}

public class Subclass
{
    public int PropertyA { get; set; }
    public string PropertyB { get; set; }
}

// result is Property1
this.NameOf((MyClass o) => o.Property1);
// result is Property2
this.NameOf((MyClass o) => o.Property2);
// result is Property3
this.NameOf((MyClass o) => o.Property3);
// result is Property4
this.NameOf((MyClass o) => o.Property4);
// result is PropertyB
this.NameOf((MyClass o) => o.Property4.PropertyB);
// result is Property5
this.NameOf((MyClass o) => o.Property5);

Es funktioniert gut mit Feldern und Eigenschaften.

Kalitsov
quelle
5

Nun, es besteht keine Notwendigkeit anzurufen .Name.ToString(), aber im Großen und Ganzen geht es darum, ja. Die einzige Überlegung, die Sie möglicherweise benötigen, ist, ob x.Foo.Bar"Foo", "Bar" oder eine Ausnahme zurückgegeben werden soll - dh, Sie müssen überhaupt iterieren.

Weitere Informationen zur flexiblen Sortierung finden Sie hier .

Marc Gravell
quelle
Ja ... es ist nur eine Sache der ersten Ebene, die zum Generieren eines Sortierspalten-Links verwendet wird. z.B. Wenn ich ein Modell habe und den zu sortierenden Spaltennamen anzeigen möchte, kann ich einen stark typisierten Link zum Objekt verwenden, um den Eigenschaftsnamen zu erhalten, für den Dynamic Linq keine Kuh hat. Prost.
Schotime
ToStringsollte hässliche Ergebnisse für unäre Ausdrücke geben.
Nawfal
3

Ich habe eine Erweiterungsmethode für ObjectStateEntry erstellt, um Eigenschaften (von Entity Framework-POCO-Klassen) als typsicher ändern zu können, da die Standardmethode nur eine Zeichenfolge akzeptiert. Hier ist meine Art, den Namen von der Unterkunft zu erhalten:

public static void SetModifiedProperty<T>(this System.Data.Objects.ObjectStateEntry state, Expression<Func<T>> action)
{
    var body = (MemberExpression)action.Body;
    string propertyName = body.Member.Name;

    state.SetModifiedProperty(propertyName);
}
Anders
quelle
3

Ich habe die INotifyPropertyChangedImplementierung ähnlich der folgenden Methode durchgeführt. Hier werden die Eigenschaften in einem Wörterbuch in der unten gezeigten Basisklasse gespeichert. Es ist natürlich nicht immer wünschenswert, die Vererbung zu verwenden, aber für Ansichtsmodelle halte ich dies für akzeptabel und gebe sehr saubere Eigenschaftsreferenzen in den Ansichtsmodellklassen.

public class PhotoDetailsViewModel
    : PropertyChangedNotifierBase<PhotoDetailsViewModel>
{
    public bool IsLoading
    {
        get { return GetValue(x => x.IsLoading); }
        set { SetPropertyValue(x => x.IsLoading, value); }
    }

    public string PendingOperation
    {
        get { return GetValue(x => x.PendingOperation); }
        set { SetPropertyValue(x => x.PendingOperation, value); }
    }

    public PhotoViewModel Photo
    {
        get { return GetValue(x => x.Photo); }
        set { SetPropertyValue(x => x.Photo, value); }
    }
}

Die etwas komplexere Basisklasse ist unten dargestellt. Es behandelt die Übersetzung vom Lambda-Ausdruck zum Eigenschaftsnamen. Beachten Sie, dass die Eigenschaften wirklich Pseudo-Eigenschaften sind, da nur die Namen verwendet werden. Es erscheint jedoch für das Ansichtsmodell transparent und verweist auf die Eigenschaften des Ansichtsmodells.

public class PropertyChangedNotifierBase<T> : INotifyPropertyChanged
{
    readonly Dictionary<string, object> _properties = new Dictionary<string, object>();

    protected U GetValue<U>(Expression<Func<T, U>> property)
    {
        var propertyName = GetPropertyName(property);

        return GetValue<U>(propertyName);
    }

    private U GetValue<U>(string propertyName)
    {
        object value;

        if (!_properties.TryGetValue(propertyName, out value))
        {
            return default(U);
        }

        return (U)value;
    }

    protected void SetPropertyValue<U>(Expression<Func<T, U>> property, U value)
    {
        var propertyName = GetPropertyName(property);

        var oldValue = GetValue<U>(propertyName);

        if (Object.ReferenceEquals(oldValue, value))
        {
            return;
        }
        _properties[propertyName] = value;

        RaisePropertyChangedEvent(propertyName);
    }

    protected void RaisePropertyChangedEvent<U>(Expression<Func<T, U>> property)
    {
        var name = GetPropertyName(property);
        RaisePropertyChangedEvent(name);
    }

    protected void RaisePropertyChangedEvent(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private static string GetPropertyName<U>(Expression<Func<T, U>> property)
    {
        if (property == null)
        {
            throw new NullReferenceException("property");
        }

        var lambda = property as LambdaExpression;

        var memberAssignment = (MemberExpression) lambda.Body;
        return memberAssignment.Member.Name;
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
Faester
quelle
1
Sie pflegen im Grunde eine Eigenschaftstasche. Nicht schlecht, aber diese Anrufe von Gettern und Setzern der Modellklasse sind etwas einfacher public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. Könnte langsamer sein, aber allgemeiner und unkomplizierter.
Nawfal
Die tatsächliche Implementierung eines einfachen Abhängigkeitseigenschaftssystems ist schwieriger (aber nicht so schwierig), aber tatsächlich viel leistungsfähiger als die obige Implementierung.
Felix K.
3

Dies ist eine weitere Antwort:

public static string GetPropertyName<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                      Expression<Func<TModel, TProperty>> expression)
    {
        var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        return metaData.PropertyName;
    }
Memo
quelle
1
ModelMetadataist im System.Web.MvcNamespace vorhanden. Vielleicht ist es nicht für den allgemeinen Fall
geeignet
3

Ich verlasse diese Funktion, wenn Sie mehrere Felder erhalten möchten:

/// <summary>
    /// Get properties separated by , (Ex: to invoke 'd => new { d.FirstName, d.LastName }')
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="exp"></param>
    /// <returns></returns>
    public static string GetFields<T>(Expression<Func<T, object>> exp)
    {
        MemberExpression body = exp.Body as MemberExpression;
        var fields = new List<string>();
        if (body == null)
        {
            NewExpression ubody = exp.Body as NewExpression;
            if (ubody != null)
                foreach (var arg in ubody.Arguments)
                {
                    fields.Add((arg as MemberExpression).Member.Name);
                }
        }

        return string.Join(",", fields);
    }
Carlos Bolivar
quelle
3
Wirst du das erklären?
1

Hier ist eine andere Möglichkeit, die PropertyInfo anhand dieser Antwort zu ermitteln. Eine Objektinstanz ist nicht mehr erforderlich.

/// <summary>
/// Get metadata of property referenced by expression. Type constrained.
/// </summary>
public static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda)
{
    return GetPropertyInfo((LambdaExpression) propertyLambda);
}

/// <summary>
/// Get metadata of property referenced by expression.
/// </summary>
public static PropertyInfo GetPropertyInfo(LambdaExpression propertyLambda)
{
    // /programming/671968/retrieving-property-name-from-lambda-expression
    MemberExpression member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));

    PropertyInfo propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));

    if(propertyLambda.Parameters.Count() == 0)
        throw new ArgumentException(String.Format(
            "Expression '{0}' does not have any parameters. A property expression needs to have at least 1 parameter.",
            propertyLambda.ToString()));

    var type = propertyLambda.Parameters[0].Type;
    if (type != propInfo.ReflectedType &&
        !type.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(String.Format(
            "Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));
    return propInfo;
}

Es kann so genannt werden:

var propertyInfo = GetPropertyInfo((User u) => u.UserID);
Hans Vonn
quelle
1

Ich habe die Antwort von @ Cameron aktualisiert , um einige Sicherheitsüberprüfungen gegen Converttypisierte Lambda-Ausdrücke aufzunehmen:

PropertyInfo GetPropertyName<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
  var body = propertyLambda.Body;
  if (!(body is MemberExpression member)
    && !(body is UnaryExpression unary
      && (member = unary.Operand as MemberExpression) != null))
    throw new ArgumentException($"Expression '{propertyLambda}' " +
      "does not refer to a property.");

  if (!(member.Member is PropertyInfo propInfo))
    throw new ArgumentException($"Expression '{propertyLambda}' " +
      "refers to a field, not a property.");

  var type = typeof(TSource);
  if (!propInfo.DeclaringType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
    throw new ArgumentException($"Expresion '{propertyLambda}' " + 
      "refers to a property that is not from type '{type}'.");

  return propInfo;
}
Shimmy Weitzhandler
quelle
1

Ab .NET 4.0 können ExpressionVisitorSie folgende Eigenschaften suchen:

class ExprVisitor : ExpressionVisitor {
    public bool IsFound { get; private set; }
    public string MemberName { get; private set; }
    public Type MemberType { get; private set; }
    protected override Expression VisitMember(MemberExpression node) {
        if (!IsFound && node.Member.MemberType == MemberTypes.Property) {
            IsFound = true;
            MemberName = node.Member.Name;
            MemberType = node.Type;
        }
        return base.VisitMember(node);
    }
}

So verwenden Sie diesen Besucher:

var visitor = new ExprVisitor();
visitor.Visit(expr);
if (visitor.IsFound) {
    Console.WriteLine("First property in the expression tree: Name={0}, Type={1}", visitor.MemberName, visitor.MemberType.FullName);
} else {
    Console.WriteLine("No properties found.");
}
dasblinkenlight
quelle
1

Dies könnte optimal sein

public static string GetPropertyName<TResult>(Expression<Func<TResult>> expr)
{
    var memberAccess = expr.Body as MemberExpression;
    var propertyInfo = memberAccess?.Member as PropertyInfo;
    var propertyName = propertyInfo?.Name;

    return propertyName;
}
Lucian Popescu
quelle
0
static void Main(string[] args)
{
    var prop = GetPropertyInfo<MyDto>(_ => _.MyProperty);

    MyDto dto = new MyDto();
    dto.MyProperty = 666;

    var value = prop.GetValue(dto);
    // value == 666
}

class MyDto
{
    public int MyProperty { get; set; }
}

public static PropertyInfo GetPropertyInfo<TSource>(Expression<Func<TSource, object>> propertyLambda)
{
    Type type = typeof(TSource);

    var member = propertyLambda.Body as MemberExpression;
    if (member == null)
    {
        var unary = propertyLambda.Body as UnaryExpression;
        if (unary != null)
        {
            member = unary.Operand as MemberExpression;
        }
    }
    if (member == null)
    {
        throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));
    }

    var propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
    {
        throw new ArgumentException(string.Format("Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));
    }

    if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType))
    {
        throw new ArgumentException(string.Format("Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(), type));
    }

    return propInfo;
}
Stas BZ
quelle