Lambda ifadesinden Özellik adı alınıyor


513

Lambda ifadesiyle iletildiğinde Mülk adını almanın daha iyi bir yolu var mı? İşte şu anda sahip olduğum şey.

Örneğin.

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

Sadece özellik bir dize olduğunda bir memberexpression olarak döküm yaparak çalıştı. çünkü tüm özellikler ben nesneyi kullanmak zorunda dizeleri değil ama o zaman onlar için bir unaryexpression dönecekti.

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;
}

Güzel kodda olduğu gibi daha iyi mi? Ben öyle düşünmüyorum. Yazım denetimi yalnızca genel ifadeye kadar uzanır, bu nedenle çalışma zamanında yaptığınız denetimlere gerçekten ihtiyacınız vardır. :(
MichaelGG

Evet ... sadece bunu yapmanın daha iyi bir yolu olup olmadığını merak ediyordum, çünkü bu bana biraz acayip geliyordu. Ama eğer bu o zaman serin. Teşekkürler.
Schotime

Yorumunuzu yeniden güncelledim; ancak dinamik bir LINQ kullanabilmeniz için bir dize almak için bir lambda kullanmak bana geriye doğru şeyler yapmamı sağlıyor ... lambda kullanıyorsanız, lambda kullanın ;-p Sorgunun tamamını tek adımda yapmak zorunda değilsiniz - kullanabilirsiniz "düzenli / lambda" OrderBy, "dinamik LINQ / string" Nerede, vb.
Marc Gravell


4
Herkes için bir not: MemberExpressionBurada listelenen yaklaşımı, yalnızca üyenin adını almak için kullanın, asıl kendisini elde etmek için değilMemberInfo , çünkü MemberInfogeri dönüşün belirli "dervied: base" senaryolarında yansıtılan tipte olacağı garanti edilmez. Bkz. Lambda-ifadesi-geri dönmüyor-beklenen-memberinfo . Beni bir kez taktı. Kabul edilen cevap da bundan muzdariptir.
nawfal

Yanıtlar:


350

Son zamanlarda bir tür güvenli OnPropertyChanged yöntemi yapmak için çok benzer bir şey yaptım.

İşte ifadenin PropertyInfo nesnesini döndürecek bir yöntem. İfade bir özellik değilse bir istisna atar.

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;
}

sourceDerleyici yöntem çağrısı tip çıkarım yapabilir böylece parametre kullanılır. Aşağıdakileri yapabilirsiniz

var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);

6
TSource ile ilgili son kontrol neden orada? Lambda şiddetle yazılmıştır, bu yüzden gerekli olduğunu düşünmüyorum.
HappyNomad

16
Ayrıca, 2012 itibariyle, tür çıkarımı kaynak parametresi olmadan iyi çalışır.
HappyNomad

4
@HappyNomad Üye olarak, üçüncü türden bir örneği olan bir nesne hayal edin. u => u.OtherType.OtherTypesPropertyson ifadenin kontrol ettiği bir durum oluşturur.
joshperry

5
Son if ifadesi: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))arayüzlere de izin vermek için olmalıdır .
Graham King

8
@GrayKing bu sadece aynı olmaz mıydı if(!propInfo.ReflectedType.IsAssignableFrom(type))?
Connell

192

Bunu yapabileceğiniz başka bir yol buldum kaynak ve özellik güçlü bir şekilde yazılmış ve lambda için giriş açıkça çıkarım oldu. Bunun doğru bir terminoloji olup olmadığından emin değilim ama sonuç burada.

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);
}

Ve sonra böyle deyin.

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

ve işte çalışıyor.
Hepinize teşekkürler.


4
Bu çözüm biraz güncellenmelidir. Lütfen aşağıdaki makaleyi kontrol edin - işte bir link
Pavel Cermak

1
Sadece ASP.Net MVC ve sadece UI katmanı (HtmlHelper) için bir seçenek.
Marc

3
c # 6.0'dan itibaren kullanabilirsinizGetInfo(nameof(u.UserId))
Vladislav

1
Net çekirdekte bunu kullanmak zorunda kaldım:var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk

146

Aynı şeyle oynuyordum ve bunu yaptım. Tam olarak test edilmedi, ancak değer türleriyle (karşılaştığınız unaryexpression sorunu) sorunu ele alıyor gibi görünüyor

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;
}

2
bunu son zamanlarda denedi ( başka bir sorudan ), alt özellikleri ele almadığını öğrendi: o => o.Thing1.Thing2dönecekti Thing2, değil Thing1.Thing2, EntityFramework içerir
drzaus

1
AKA (field.Body UnaryExpression? ((UnaryExpression) field.Body) .Operand: field.Body) MemberExpression olarak

51
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}

Bu, üye ve tekli ifadeleri işler. UnaryExpressionFarkınız, ifadeniz bir değer türünü temsil ediyorsa bir elde edersiniz , ifadeniz ise MemberExpressionbir referans türünü temsil ediyorsa bir alırsınız . Her şey bir nesneye aktarılabilir, ancak değer türleri kutu içine alınmalıdır. Bu yüzden UnaryExpression var. Referans.

Okunabilirlik ortamları için (@Jowen), işte genişletilmiş bir eşdeğer:

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;
}

@flem, okunabilirlik için <TField> 'ı atladım, herhangi bir sorun var mı? LambdaExpressions.GetName <Basket> (m => m.Kalite)
Soren

1
@soren Eminim benden daha fazla ayarlanmış biri, değer türlerinin ifadelerini iletirken kodunuzu gereksiz boks / kutu açma potansiyeline kadar açmanızı önerebilir, ancak ifade bu yöntemde asla derlenip değerlendirilmediğinden, muhtemelen sorun değil.
Paul Fleming

30

C # 7 desen eşleşmesi ile:

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());
    }
}

Misal:

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

[Güncelleme] C # 8 desen eşleştirme:

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())
    };


20

Bu, alanların / özelliklerin / dizinleyicilerin / yöntemlerin / uzantı yöntemlerinin / yapı delegelerinin / sınıf / arabirim / delege / dizenin dize adını almak için genel bir uygulamadır. Statik / örnek ve jenerik olmayan / jenerik varyantların kombinasyonları ile test yaptım.

//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);
}

Bu şey basit bir whiledöngüde de yazılabilir :

//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");
        }
    }
}

Yinelemeli yaklaşımı seviyorum, ancak ikincisini okumak daha kolay olabilir. Birisi şöyle diyebilir:

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();

son üyeyi yazdırmak için.

Not:

  1. Zincirleme ifadeler durumunda A.B.C"C" döndürülür.

  2. Bu, consts, dizi indeksleyicileri veya enums ile çalışmaz (tüm durumları kapsamak imkansızdır).


19

Array.Length söz konusu olduğunda bir kenar durumu var . 'Uzunluk' bir özellik olarak gösterilse de, daha önce önerilen çözümlerin hiçbirinde kullanamazsınız.

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);
}

Şimdi örnek kullanım:

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

PropertyNameFromUnaryExprKontrol edilmezse ArrayLength, "someArray" konsola yazdırılır (derleyici, Debug'da bile özel bir optimizasyon olarak destek Uzunluğu alanına doğrudan erişim sağlıyor gibi görünüyor , bu nedenle özel durum).


16

İşte Cameron tarafından önerilen yöntemin bir güncellemesi . İlk parametre gerekli değildir.

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;
}

Aşağıdakileri yapabilirsiniz:

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

Genişletme yöntemleri:

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;
}

Yapabilirsin:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);

Hayır u, bir tür olarak çıkarım yapmaz , bunu yapamaz çünkü çıkarım yapacak tür yoktur. YapabileceklerinizGetPropertyInfo<SomeType>(u => u.UserID)
Lucas

14

Ben bazı gördük cevap önerilerini içine detaya MemberExpression/ 'UnaryExpression / alt özelliklerini iç içe yakalamaz.

ex) yerine o => o.Thing1.Thing2döndürürThing1Thing1.Thing2 .

EntityFramework ile çalışmaya çalışıyorsanız bu ayrım önemlidir DbSet.Include(...) .

Ben sadece ayrıştırma Expression.ToString()iyi ve nispeten hızlı çalışır gibi görünüyor buldum . Ben karşı karşılaştırıldığında UnaryExpressionsürümü ve hatta alma ToStringoffMember/UnaryExpression olduğu hızlı olup olmadığını görmek için, ama fark ihmal edilebilir oldu. Bu korkunç bir fikirse lütfen beni düzeltin.

Genişletme Yöntemi

/// <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

(Sınırlayıcıyı kontrol etmek bile aşırı olabilir)

Demo (LinqPad)

Gösteri + Karşılaştırma kodu - https://gist.github.com/zaus/6992590


1
+ 1 çok ilginç. Bu yöntemi kendi kodunuzda kullanmaya devam ettiniz mi? iyi çalışıyor mu? herhangi bir uç durum keşfettiniz mi?
Benjamin Gale

Fikrini göremiyorum. Bağladığınız cevaba göre söylediğiniz gibi o => o.Thing1.Thing2geri dönmez Thing1ama Thing2. Aslında cevabınız Thing1.Thing2istenebilecek veya istenmeyecek bir şey döndürür .
nawfal

Kılıfla ilgili çalışmıyor korman uyarıları: stackoverflow.com/a/11006147/661933 . Kesmek önlemek için her zaman daha iyi.
nawfal

@nawfal # 1 - asıl sorun, istediğiniz Thing1.Thing2 , asla Thing1. Ben yüklemin değeri olan Thing2anlamını söyledim . Bu niyeti yansıtacak şekilde cevabı güncelleyeceğim. o.Thing1.Thing2
drzaus

@drzaus üzgünüm hala seni alamıyorum. Gerçekten anlamaya çalışıyorum. Neden diğer cevapların geri geldiğini söyledin Thing1? Bunu hiç geri çekmediğini sanmıyorum.
nawfal

6

Ben ön C # 6 projeleri için bir uzatma yöntemi ve C # 6 hedefleyenler için nameof () kullanıyorum .

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;
    }
}

Ve şöyle diyorum:

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);

Hem alan hem de özelliklerle iyi çalışır.


5

Aramaya gerek yok .Name.ToString(), ama genel olarak bununla ilgili, evet. İhtiyaç duyabileceğiniz tek şey, x.Foo.Bar"Foo", "Bar" veya bir istisna döndürüp döndürmemenizdir.

Esnek sıralama hakkında daha fazla bilgi için buraya bakın .


Evet ... bu sadece birinci seviye bir şey, bir sıralama sütunu bağlantısı oluşturmak için kullanılıyor. Örneğin. Ben bir model varsa ve ben sıralamak için sütun adını görüntülemek istiyorum i dinamik linq üzerinde inek olmayacak özellik adını almak için nesneye güçlü bir şekilde yazılmış bir bağlantı kullanabilirsiniz. şerefe.
Schotime

ToStringtekli ifadeler için çirkin sonuçlar vermelidir.
nawfal

3

Varsayılan yöntem yalnızca bir dize kabul ettiğinden, özelliklerini (Entity Framework POCO sınıflarının) güvenli bir şekilde değiştirilmiş olarak işaretlemek için ObjectStateEntry üzerinde bir uzantı yöntemi oluşturdu. İşte mülkten adı almanın yolu:

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);
}

3

INotifyPropertyChangedUygulamayı aşağıdaki yönteme benzer şekilde yaptım . Burada özellikler, aşağıda gösterilen temel sınıftaki bir sözlükte saklanır. Elbette kalıtım kullanmak her zaman arzu edilmez, ancak görünüm modelleri için kabul edilebilir olduğunu ve görünüm modeli sınıflarında çok temiz özellik referansları verdiğini düşünüyorum.

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); }
    }
}

Biraz daha karmaşık temel sınıf aşağıda gösterilmiştir. Lambda ifadesinden özellik adına çeviriyi işler. Yalnızca adlar kullanıldığından, özelliklerin gerçekten sözde özellikler olduğunu unutmayın. Ancak görünüm modeline ve görünüm modelindeki özelliklere referanslara şeffaf görünecektir.

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;
}

1
Temelde bir eşya çantası bulunduruyorsun. Fena değil, ancak model sınıfının alıcılarından ve ayarlayıcılarından gelen çağrılar biraz daha kolay public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. Daha yavaş, ancak daha genel ve anlaşılır olabilir.
nawfal

Aslında basit bir bağımlılık özelliği sistemi uygulamak daha zordur (ancak çok zor değildir), ancak aslında yukarıdaki uygulamadan çok daha performanslıdır.
Felix K.

3

Bu başka bir cevap:

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;
    }

1
ModelMetadatamevcut olan System.Web.Mvcad. Belki genel durum için uygun değildir
asakura89 23:15

3

Birden fazla alan almak istiyorsanız bu işlevden ayrılırım:

/// <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);
    }

3
Bunu açıklayacak mısın?

1

İşte bu cevabı temel alarak PropertyInfo almanın başka bir yolu . Bir nesne örneği ihtiyacını ortadan kaldırır.

/// <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;
}

Bu şekilde çağrılabilir:

var propertyInfo = GetPropertyInfo((User u) => u.UserID);

1

@ Cameron'un cevabınıConvert yazılan lambda ifadelerine karşı bazı güvenlik kontrolleri içerecek şekilde güncelledim :

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;
}

1

.NET 4.0 ile başlayarak ExpressionVisitorözellikleri bulmak için kullanabilirsiniz :

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);
    }
}

Bu ziyaretçiyi nasıl kullandığınız aşağıda açıklanmıştır:

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.");
}

1

Bu en uygun olabilir

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;
}

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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.