Lấy tên thuộc tính từ biểu thức lambda


513

Có cách nào tốt hơn để có được tên Thuộc tính khi được truyền qua biểu thức lambda không? Đây là những gì tôi hiện đang có.

ví dụ.

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

Nó hoạt động bằng cách sử dụng nó như một bản ghi nhớ chỉ khi thuộc tính là một chuỗi. bởi vì không phải tất cả các thuộc tính là các chuỗi tôi phải sử dụng đối tượng nhưng sau đó nó sẽ trả về một biểu thức unaryexpression cho các thuộc tính đó.

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

Tốt hơn như trong mã đẹp hơn? Tôi không nghĩ vậy. Việc đánh máy chỉ mở rộng cho biểu thức tổng thể, vì vậy bạn thực sự cần kiểm tra bạn có trong thời gian chạy. :(
MichaelGG

Vâng ... chỉ là tự hỏi liệu có cách nào tốt hơn để làm điều đó không, vì tôi cảm thấy hơi khó chịu với tôi. Nhưng nếu đó là mát mẻ. cảm ơn.
Schotime

Tôi cập nhật lại bình luận của bạn; nhưng sử dụng lambda để lấy chuỗi để bạn có thể sử dụng LINQ động khiến tôi phải làm điều ngược lại ... nếu bạn sử dụng lambda, hãy sử dụng lambda ;-p Bạn không phải thực hiện toàn bộ truy vấn trong một bước - bạn có thể sử dụng OrderBy "thường xuyên / lambda", "LINQ / chuỗi động" ở đâu, v.v.
Marc Gravell

1
bản sao có thể của get-property-name-and-type-
used

4
Một lưu ý cho mọi người: Chỉ sử dụng MemberExpressioncách tiếp cận được liệt kê ở đây để lấy tên của thành viên, chứ không phải để lấy MemberInfochính thực tế , vì MemberInfotrả lại không được đảm bảo là loại được phản ánh trong các tình huống "dervied: base" nhất định. Xem lambda-biểu hiện-không trở lại-mong đợi-thành viêninfo . Vượt tôi một lần. Câu trả lời được chấp nhận quá chịu đựng điều này.
nawfal

Câu trả lời:


350

Gần đây tôi đã làm một điều rất giống để tạo ra một phương thức OnPropertyChanged an toàn.

Đây là một phương thức sẽ trả về đối tượng PropertyInfo cho biểu thức. Nó ném một ngoại lệ nếu biểu thức không phải là một tài sản.

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

Các sourcetham số được sử dụng để trình biên dịch có thể làm suy luận kiểu trên lời gọi phương thức. Bạn có thể làm như sau

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

6
Tại sao kiểm tra cuối cùng liên quan đến TSource trong đó? Lambda được gõ mạnh nên tôi không nghĩ nó cần thiết.
HappyNomad

16
Ngoài ra, kể từ năm 2012, kiểu suy luận hoạt động tốt mà không cần tham số nguồn.
HappyNomad

4
@HappyNomad Hãy tưởng tượng một đối tượng có tư cách là thành viên, thể hiện của loại thứ ba. u => u.OtherType.OtherTypesPropertysẽ tạo ra một trường hợp như vậy mà tuyên bố cuối cùng đang kiểm tra.
joshperry

5
Câu lệnh if cuối cùng sẽ là: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))cho phép các giao diện quá.
Graham King

8
@GrayKing sẽ không giống như if(!propInfo.ReflectedType.IsAssignableFrom(type))vậy sao?
Connell

192

Tôi tìm thấy một cách khác mà bạn có thể làm là để nguồn và tài sản được gõ mạnh và suy luận rõ ràng đầu vào cho lambda. Không chắc đó có phải là thuật ngữ chính xác hay không nhưng đây là kết quả.

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

Và sau đó gọi nó như vậy.

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

và voila nó hoạt động.
Cảm ơn tất cả.


4
Giải pháp này nên được cập nhật một chút. Vui lòng kiểm tra bài viết sau - đây là một liên kết
Pavel Cermak

1
Đây chỉ là một tùy chọn nếu bạn thực hiện ASP.Net MVC và chỉ dành cho lớp UI (HtmlHelper).
Marc

3
bắt đầu từ c # 6.0 bạn có thể sử dụngGetInfo(nameof(u.UserId))
Vladislav

1
Trong lõi mạng tôi phải sử dụng cái này:var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk

146

Tôi đã chơi xung quanh với điều tương tự và làm việc này lên. Nó không được kiểm tra đầy đủ nhưng dường như xử lý vấn đề với các loại giá trị (vấn đề không áp dụng mà bạn gặp phải)

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
đã thử điều này gần đây (từ một câu hỏi khác ), phát hiện ra nó không xử lý các vấn đề phụ: o => o.Thing1.Thing2sẽ trả lại Thing2, không Thing1.Thing2, điều này không đúng nếu bạn đang cố sử dụng nó trong
EntityFramework

1
AKA (trường.Body là UnaryExpression? ((UnaryExpression) trường.Body) .Operand: field.Body) với tư cách 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;
}

Điều này xử lý các thành viên và unary biểu thức. Sự khác biệt là bạn sẽ nhận được UnaryExpressionnếu biểu thức của bạn đại diện cho một loại giá trị trong khi bạn sẽ nhận được MemberExpressionnếu biểu thức của bạn đại diện cho một loại tham chiếu. Mọi thứ có thể được truyền tới một đối tượng, nhưng các loại giá trị phải được đóng hộp. Đây là lý do tại sao UnaryExpression tồn tại. Tài liệu tham khảo.

Đối với các mức độ dễ đọc (@Jowen), đây là một tương đương mở rộng:

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, tôi bỏ qua <TField> để dễ đọc, có vấn đề gì không. LambdaExpressions.GetName <Basket> (m => m.Quantity)
Soren

1
@soren Tôi chắc chắn rằng ai đó đã điều chỉnh nhiều hơn tôi có thể đề nghị rằng bạn đang mở mã của mình với tiềm năng của quyền anh / unboxing không cần thiết khi truyền biểu thức của các loại giá trị, nhưng vì biểu thức không bao giờ được biên dịch và đánh giá trong phương thức này, nó có lẽ không phải là một vấn đề
Paul Fleming

30

Với khớp mẫu 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());
    }
}

Thí dụ:

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

[Cập nhật] Khớp mẫu 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())
    };


20

Đây là một triển khai chung để lấy tên chuỗi của các trường / thuộc tính / bộ chỉ mục / phương thức / phương thức mở rộng / đại biểu của struct / class / interface / ủy nhiệm / mảng. Tôi đã thử nghiệm với sự kết hợp của các biến thể tĩnh / thể hiện và không chung chung / chung chung.

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

Điều này cũng có thể được viết trong một whilevòng lặp đơn giản :

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

Tôi thích cách tiếp cận đệ quy, mặc dù cách thứ hai có thể dễ đọc hơn. Người ta có thể gọi nó như:

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

để in thành viên cuối cùng.

Ghi chú:

  1. Trong trường hợp các biểu thức được xâu chuỗi như A.B.C, "C" được trả về.

  2. Điều này không hoạt động với consts, bộ chỉ mục mảng hoặc enums (không thể bao gồm tất cả các trường hợp).


19

Có một trường hợp cạnh khi nói đến Array.Lipse. Mặc dù 'Độ dài' được hiển thị dưới dạng một thuộc tính, bạn không thể sử dụng nó trong bất kỳ giải pháp được đề xuất nào trước đây.

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

Bây giờ sử dụng ví dụ:

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

Nếu PropertyNameFromUnaryExprkhông kiểm tra ArrayLength, "someArray" sẽ được in ra bàn điều khiển (trình biên dịch dường như tạo ra quyền truy cập trực tiếp vào trường Độ dài sao lưu , dưới dạng tối ưu hóa, ngay cả trong Debug, do đó là trường hợp đặc biệt).


16

Đây là bản cập nhật cho phương pháp được đề xuất bởi Cameron . Tham số đầu tiên là không bắt buộc.

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

Bạn có thể làm như sau:

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

Phương pháp mở rộng:

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

Bạn có thể:

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

Không, anh ta sẽ không suy luận unhư một số loại, anh ta không thể làm điều đó bởi vì không có loại để suy luận. Những gì bạn có thể làm làGetPropertyInfo<SomeType>(u => u.UserID)
Lucas

14

Tôi đã thấy rằng một số câu trả lời được đề xuất đi sâu vào MemberExpression/ UnaryExpressionkhông nắm bắt được lồng nhau / các sản phẩm phụ.

ex) o => o.Thing1.Thing2trả về Thing1chứ không phải Thing1.Thing2.

Sự khác biệt này rất quan trọng nếu bạn đang cố gắng làm việc với EntityFramework DbSet.Include(...).

Tôi đã thấy rằng chỉ cần phân tích cú pháp Expression.ToString()dường như hoạt động tốt, và tương đối nhanh chóng. Tôi đã so sánh nó với UnaryExpressionphiên bản, và thậm chí ToStringrời khỏi Member/UnaryExpressionđể xem liệu nó có nhanh hơn không, nhưng sự khác biệt là không đáng kể. Xin hãy sửa cho tôi nếu đây là một ý tưởng khủng khiếp.

Phương pháp mở rộng

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

(Kiểm tra dấu phân cách thậm chí có thể là quá mức cần thiết)

Bản demo (LinqPad)

Trình diễn + Mã so sánh - https://gist.github.com/zaus/6992590


1
+ 1 rất thú vị. Bạn đã tiếp tục sử dụng phương pháp này trong mã của riêng bạn? nó có hoạt động tốt không Bạn đã phát hiện ra trường hợp cạnh?
Benjamin Gale

Tôi không thấy ý tưởng của bạn. Đi theo câu trả lời bạn liên kết o => o.Thing1.Thing2không trở lại Thing1như bạn nói nhưng Thing2. Trong thực tế, câu trả lời của bạn trả về một cái gì đó giống như Thing1.Thing2có thể hoặc không thể mong muốn.
nawfal

Không hoạt động với trường hợp cảnh báo korman: stackoverflow.com/a/11006147/661933 . Luôn luôn tốt hơn để tránh hack.
nawfal

@nawfal # 1 - vấn đề ban đầu là bạn muốn Thing1.Thing2 , không bao giờ Thing1. Tôi đã nói Thing2có nghĩa là giá trị của o.Thing1.Thing2, đó là điểm của vị ngữ. Tôi sẽ cập nhật câu trả lời để phản ánh ý định đó.
drzaus

@drzaus xin lỗi tôi vẫn không nhận được bạn. Thực sự cố gắng để hiểu. Tại sao bạn lại nói rằng những câu trả lời khác ở đây trở lại Thing1? Tôi không nghĩ rằng nó rút lại điều đó cả.
nawfal

6

Tôi đang sử dụng một phương thức mở rộng cho các dự án C # 6 trước và nameof () cho những người nhắm mục tiêu 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;
    }
}

Và tôi gọi nó như:

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

Nó hoạt động tốt với cả các lĩnh vực và tài sản.


5

Vâng, không cần phải gọi .Name.ToString(), nhưng nói chung là về nó, vâng. Việc cân nhắc duy nhất bạn có thể cần là liệu x.Foo.Barnên trả về "Foo", "Bar" hay ngoại lệ - tức là bạn có cần lặp đi lặp lại không.

(bình luận lại) để biết thêm về cách sắp xếp linh hoạt, xem tại đây .


Vâng ... nó chỉ là một thứ cấp đầu tiên, được sử dụng để tạo liên kết cột sắp xếp. ví dụ. Nếu tôi có một mô hình và tôi muốn hiển thị tên cột để sắp xếp theo thì tôi có thể sử dụng một liên kết được gõ mạnh đến đối tượng để lấy tên thuộc tính mà linq động sẽ không có bò. chúc mừng
Schotime

ToStringnên cho kết quả xấu cho các biểu hiện đơn nhất.
nawfal

3

Tôi đã tạo một phương thức mở rộng trên ObjectStateEntry để có thể gắn cờ các thuộc tính (của các lớp POCO của Entity Framework) như được sửa đổi theo cách an toàn kiểu, vì phương thức mặc định chỉ chấp nhận một chuỗi. Đây là cách của tôi để có được tên từ tài sản:

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

Tôi đã thực hiện việc INotifyPropertyChangedthực hiện tương tự như phương pháp dưới đây. Ở đây các thuộc tính được lưu trữ trong một từ điển trong lớp cơ sở được hiển thị bên dưới. Tất nhiên không phải lúc nào cũng mong muốn sử dụng tính kế thừa, nhưng đối với các mô hình xem tôi nghĩ nó có thể chấp nhận được và cung cấp các tham chiếu thuộc tính rất sạch trong các lớp mô hình xem.

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

Lớp cơ sở có phần phức tạp hơn được hiển thị dưới đây. Nó xử lý bản dịch từ biểu thức lambda sang tên thuộc tính. Lưu ý rằng các thuộc tính thực sự là các thuộc tính giả vì chỉ các tên được sử dụng. Nhưng nó sẽ xuất hiện trong suốt đối với mô hình khung nhìn và các tham chiếu đến các thuộc tính trên mô hình khung nhìn.

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
Về cơ bản bạn đang duy trì một túi tài sản. Không tệ, nhưng những cuộc gọi từ getters và setters của lớp mô hình thì dễ dàng hơn một chút public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. Có thể chậm hơn, nhưng chung chung và đơn giản hơn.
nawfal

Trên thực tế, việc thực hiện một hệ thống thuộc tính phụ thuộc đơn giản khó hơn (nhưng không quá khó) nhưng thực sự hiệu quả hơn nhiều so với việc thực hiện ở trên.
Felix K.

3

Đây là một câu trả lời khác:

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
ModelMetadatatồn tại trong System.Web.Mvckhông gian tên. Có lẽ nó không phù hợp với trường hợp chung
asakura89

3

Tôi rời khỏi chức năng này nếu bạn muốn nhận nhiều trường:

/// <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
Bạn sẽ giải thích điều này?

1

Đây là một cách khác để có được PropertyInfo dựa trên câu trả lời này. Nó loại bỏ sự cần thiết cho một đối tượng.

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

Nó có thể được gọi như vậy:

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

1

Tôi đã cập nhật câu trả lời của @ Cameron để bao gồm một số kiểm tra an toàn đối với Convertcác biểu thức lambda đã nhập:

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

Bắt đầu với .NET 4.0, bạn có thể sử dụng ExpressionVisitorđể tìm các thuộc tính:

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

Đây là cách bạn sử dụng khách truy cập này:

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

Điều này có thể là tối ưu

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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.