Mengambil nama Properti dari ekspresi lambda

513

Apakah ada cara yang lebih baik untuk mendapatkan nama Properti saat dilewatkan melalui ekspresi lambda? Inilah yang saya miliki saat ini.

misalnya.

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

Itu bekerja dengan melemparkannya sebagai ekspresi anggota hanya ketika properti itu adalah string. karena tidak semua properti adalah string saya harus menggunakan objek tetapi kemudian akan mengembalikan unaryexpression untuk itu.

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
sumber
Lebih baik dalam kode yang lebih bagus? Saya kira tidak. Pemeriksaan ketik hanya meluas ke ekspresi keseluruhan, jadi Anda benar-benar membutuhkan pemeriksaan yang Anda miliki saat runtime. :(
MichaelGG
Ya ... hanya bertanya-tanya apakah ada cara yang lebih baik untuk melakukannya, karena rasanya agak berantakan bagi saya. Tapi kalau itu keren maka. Terima kasih.
Schotime
Saya memperbarui komentar Anda; tetapi menggunakan lambda untuk mendapatkan string sehingga Anda dapat menggunakan LINQ dinamis menganggap saya melakukan hal-hal mundur ... jika Anda menggunakan lambda, gunakan lambda ;-p Anda tidak harus melakukan seluruh permintaan dalam satu langkah - Anda bisa menggunakan "biasa / lambda" orderby, "LINQ dinamis / string" di mana, dll
Marc Gravell
1
kemungkinan duplikat dari get-property-name-and-type-using-lambda-expression
nawfal
4
Catatan untuk semua orang: Gunakan MemberExpressionpendekatan yang tercantum di sini hanya untuk mendapatkan nama anggota, bukan untuk mendapatkan yang sebenarnya MemberInfo, karena yang MemberInfodikembalikan tidak dijamin dari tipe yang tercermin dalam skenario "dervied: base" tertentu. Lihat lambda-ekspresi-tidak-kembali-diharapkan-anggota-info . Membuatku tersandung sekali. Jawaban yang diterima juga menderita dari ini.
nawfal

Jawaban:

350

Saya baru-baru ini melakukan hal yang sangat mirip untuk membuat metode OnPropertyChanged jenis yang aman.

Berikut adalah metode yang akan mengembalikan objek PropertyInfo untuk ekspresi. Itu melempar pengecualian jika ekspresi bukan properti.

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

The sourceparameter digunakan sehingga compiler dapat melakukan inferensi tipe pada metode panggilan. Anda dapat melakukan hal berikut

var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
Cameron MacFarland
sumber
6
Mengapa cek terakhir tentang TSource ada di sana? Lambda sangat diketik jadi saya pikir itu tidak perlu.
HappyNomad
16
Juga, pada 2012, inferensi tipe berfungsi dengan baik tanpa parameter sumber.
HappyNomad
4
@HappyNomad Bayangkan sebuah objek yang memiliki anggota, instance dari tipe ketiga. u => u.OtherType.OtherTypesPropertyakan membuat kasus bahwa pernyataan terakhir sedang memeriksa.
joshperry
5
Pernyataan if terakhir seharusnya: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))untuk memungkinkan antarmuka juga.
Graham King
8
@ GrayKing bukankah itu sama dengan adil if(!propInfo.ReflectedType.IsAssignableFrom(type))?
Connell
192

Saya menemukan cara lain yang dapat Anda lakukan adalah memiliki sumber dan properti sangat diketik dan secara eksplisit menyimpulkan input untuk lambda. Tidak yakin apakah itu terminologi yang benar tetapi inilah hasilnya.

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

Dan menyebutnya seperti itu.

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

dan voila itu bekerja.
Terima kasih semuanya.

Schotime
sumber
4
Solusi ini harus sedikit diperbarui. Silakan periksa artikel berikut - di sini ada tautan
Pavel Cermak
1
Ini hanya pilihan jika Anda melakukan ASP.Net MVC dan hanya untuk lapisan UI (HtmlHelper).
Marc
3
mulai dari c # 6.0 Anda dapat menggunakanGetInfo(nameof(u.UserId))
Vladislav
1
Dalam inti bersih saya harus menggunakan ini:var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk
147

Saya bermain-main dengan hal yang sama dan menyelesaikannya. Ini tidak sepenuhnya diuji tetapi tampaknya menangani masalah dengan tipe nilai (masalah unaryexpression yang Anda temui)

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
sumber
2
mencoba ini baru-baru ini (dari pertanyaan lain ), ternyata tidak menangani subproperti: o => o.Thing1.Thing2akan kembali Thing2, tidak Thing1.Thing2, yang tidak benar jika Anda mencoba menggunakannya di EntityFramework termasuk
drzaus
1
AKA (field.Body is UnaryExpression? ((UnaryExpression) field.Body) .Operand: field.Body) sebagai 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;
}

Ini menangani ekspresi anggota dan unary. Perbedaannya adalah bahwa Anda akan mendapatkan UnaryExpressionjika ekspresi Anda mewakili tipe nilai sedangkan Anda akan mendapatkan MemberExpressionjika ekspresi Anda mewakili tipe referensi. Semuanya bisa dilemparkan ke objek, tetapi tipe nilai harus kotak. Inilah sebabnya mengapa UnaryExpression ada. Referensi.

Demi keterbacaan (@Jowen), berikut ini ekuivalen yang diperluas:

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
sumber
@ flem, saya menghilangkan <TField> agar mudah dibaca, apakah ada masalah. LambdaExpressions.GetName <Basket> (m => m.Quantity)
Soren
1
@soren Saya yakin seseorang yang lebih teliti daripada saya dapat menyarankan Anda membuka kode Anda hingga potensi tinju / unboxing yang tidak perlu ketika melewati ekspresi tipe nilai, tetapi karena ekspresi tidak pernah dikompilasi dan dievaluasi dalam metode ini, itu mungkin bukan masalah.
Paul Fleming
30

Dengan pencocokan pola C # 7:

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

Contoh:

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

[Perbarui] pencocokan pola C # 8:

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
sumber
20

Ini adalah implementasi umum untuk mendapatkan nama string bidang / properti / pengindeks / metode / metode ekstensi / delegasi dari struct / class / interface / delegate / array. Saya telah menguji dengan kombinasi varian static / instance dan non-generik / generik.

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

Hal ini dapat ditulis dalam satu whileloop sederhana juga:

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

Saya suka pendekatan rekursif, meskipun yang kedua mungkin lebih mudah dibaca. Orang bisa menyebutnya seperti:

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

untuk mencetak anggota terakhir.

catatan:

  1. Dalam kasus ekspresi berantai seperti A.B.C, "C" dikembalikan.

  2. Ini tidak berfungsi dengan consts, indexer array atau enums (tidak mungkin untuk mencakup semua kasus).

nawfal
sumber
19

Ada kasus tepi ketika datang ke Array. Panjang. Meskipun 'Panjang' ditampilkan sebagai properti, Anda tidak dapat menggunakannya di salah satu solusi yang diusulkan sebelumnya.

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

Sekarang contoh penggunaan:

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

Jika PropertyNameFromUnaryExprtidak memeriksa ArrayLength, "someArray" akan dicetak ke konsol (kompiler tampaknya menghasilkan akses langsung ke bidang Panjang dukungan , sebagai pengoptimalan, bahkan di Debug, dengan demikian kasus khusus).

kornman00
sumber
16

Berikut ini adalah pembaruan untuk metode yang diusulkan oleh Cameron . Parameter pertama tidak diperlukan.

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

Anda dapat melakukan hal berikut:

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

Metode ekstensi:

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

Kamu bisa:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);
Adrian
sumber
Tidak, dia tidak akan menyimpulkan usebagai tipe tertentu, dia tidak bisa melakukan itu karena tidak ada tipe untuk menyimpulkan. Apa yang dapat Anda lakukan adalahGetPropertyInfo<SomeType>(u => u.UserID)
Lucas
14

Saya telah menemukan bahwa beberapa jawaban yang disarankan yang menelusuri ke MemberExpression/UnaryExpression tidak menangkap bersarang / subproperti.

ex) o => o.Thing1.Thing2mengembalikan Thing1daripadaThing1.Thing2 .

Perbedaan ini penting jika Anda mencoba untuk bekerja dengan EntityFramework DbSet.Include(...) .

Saya telah menemukan bahwa hanya mengurai Expression.ToString()tampaknya berfungsi dengan baik, dan relatif cepat. Saya membandingkannya dengan UnaryExpressionversi, dan bahkan ToStringturun dariMember/UnaryExpression untuk melihat apakah itu lebih cepat, tetapi perbedaannya dapat diabaikan. Harap perbaiki saya jika ini adalah ide yang buruk.

Metode Perpanjangan

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

(Memeriksa pembatas bahkan mungkin berlebihan)

Demo (LinqPad)

Demonstrasi + Kode perbandingan - https://gist.github.com/zaus/6992590

drzaus
sumber
1
+1 sangat menarik. Apakah Anda terus menggunakan metode ini dalam kode Anda sendiri? apakah ini berhasil ok? Pernahkah Anda menemukan kasus tepi?
Benjamin Gale
Saya gagal melihat ide Anda. Mengikuti jawaban yang Anda tautkan o => o.Thing1.Thing2tidak kembali Thing1seperti yang Anda katakan tetapi Thing2. Sebenarnya jawaban Anda mengembalikan sesuatu seperti Thing1.Thing2yang mungkin atau mungkin tidak diinginkan.
nawfal
Tidak bekerja dengan case korman memperingatkan: stackoverflow.com/a/11006147/661933 . Selalu lebih baik untuk menghindari peretasan.
nawfal
@nawfal # 1 - masalah aslinya adalah yang Anda inginkan Thing1.Thing2 , tidak pernah Thing1. Saya mengatakan Thing2yang berarti nilai dari o.Thing1.Thing2, yang merupakan titik predikat. Saya akan memperbarui jawaban untuk mencerminkan niat itu.
drzaus
@drzaus maaf saya masih belum mengerti Anda. Benar-benar berusaha memahami. Mengapa Anda mengatakan bahwa jawaban lain di sini kembali Thing1? Saya tidak berpikir itu mengembalikannya sama sekali.
nawfal
6

Saya menggunakan metode ekstensi untuk proyek pra C # 6 dan nama () untuk mereka yang menargetkan C # 6.

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

Dan saya menyebutnya seperti:

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

Ini berfungsi baik dengan bidang dan properti.

kalitsov
sumber
5

Yah, tidak perlu menelepon .Name.ToString(), tapi secara luas itu saja, ya. Satu-satunya pertimbangan yang mungkin Anda butuhkan adalah apakah x.Foo.Barharus mengembalikan "Foo", "Bar", atau pengecualian - yaitu apakah Anda perlu mengulangi sama sekali.

(komentar ulang) untuk informasi lebih lanjut tentang penyortiran fleksibel, lihat di sini .

Marc Gravell
sumber
Ya ... ini hanya hal tingkat pertama, yang digunakan untuk menghasilkan tautan kolom sortir. misalnya. Jika saya memiliki model dan saya ingin menampilkan nama kolom untuk disortir oleh saya dapat menggunakan tautan yang sangat diketik ke objek untuk mendapatkan nama properti yang linq dinamisnya tidak akan memiliki cow over. Bersulang.
Schotime
ToStringharus memberikan hasil yang buruk untuk ekspresi unary.
nawfal
3

Saya membuat metode ekstensi pada ObjectStateEntry untuk dapat menandai properti (dari kelas POCO Entity Framework) yang dimodifikasi dalam jenis yang aman, karena metode default hanya menerima string. Inilah cara saya mendapatkan nama dari properti:

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
sumber
3

Saya telah melakukan INotifyPropertyChangedimplementasi yang mirip dengan metode di bawah ini. Di sini properti disimpan dalam kamus di kelas dasar yang ditunjukkan di bawah ini. Tentu saja tidak selalu diinginkan untuk menggunakan warisan, tetapi untuk model tampilan saya pikir itu dapat diterima dan memberikan referensi properti yang sangat bersih di kelas model tampilan.

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

Kelas dasar yang agak lebih kompleks ditunjukkan di bawah ini. Ini menangani terjemahan dari ekspresi lambda ke nama properti. Perhatikan bahwa properti tersebut adalah properti semu karena hanya nama yang digunakan. Tetapi akan terlihat transparan untuk model tampilan dan referensi ke properti pada model tampilan.

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
sumber
1
Anda pada dasarnya memelihara tas properti. Tidak buruk, tetapi panggilan-panggilan dari getter dan setter kelas model sedikit lebih mudah public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. Bisa lebih lambat, tetapi lebih umum dan mudah.
nawfal
Sebenarnya menerapkan sistem properti dependensi sederhana lebih sulit (tapi tidak terlalu sulit) tetapi sebenarnya jauh lebih berkinerja daripada implementasi di atas.
Felix K.
3

Ini jawaban lain:

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
sumber
1
ModelMetadataada di System.Web.Mvcnamespace. Mungkin tidak cocok untuk kasus umum
asakura89
3

Saya meninggalkan fungsi ini jika Anda ingin mendapatkan beberapa bidang:

/// <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
sumber
3
Apakah Anda akan menjelaskan ini?
1

Berikut adalah cara lain untuk mendapatkan PropertyInfo berdasarkan jawaban ini. Ini menghilangkan kebutuhan akan instance objek.

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

Ini bisa disebut seperti ini:

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

Saya telah memperbarui jawaban @ Cameron untuk memasukkan beberapa pemeriksaan keamanan terhadap Convertekspresi lambda yang diketik:

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
sumber
1

Mulai dengan .NET 4.0 yang dapat Anda gunakan ExpressionVisitoruntuk menemukan properti:

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

Inilah cara Anda menggunakan pengunjung ini:

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
sumber
1

Ini mungkin optimal

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