람다 식에서 속성 이름 검색


512

람다 식을 통해 전달 될 때 속성 이름을 얻는 더 좋은 방법이 있습니까? 여기 내가 현재 가지고있는 것입니다.

예.

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

속성이 문자열 일 때만 멤버 표현식으로 캐스팅하여 작동했습니다. 모든 속성이 문자열이 아니기 때문에 객체를 사용해야했지만 그에 대한 단항 표현식을 반환합니다.

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

더 좋은 코드처럼 더 좋습니까? 나는 그렇게 생각하지 않습니다. 유형 검사는 전체 표현식으로 만 확장되므로 실제로 런타임에 검사가 필요합니다. :(
— MichaelGG

그래 .. 나에게 약간 해키 느낌이 들기 때문에 더 좋은 방법이 있는지 궁금해하고있었습니다. 그러나 그렇다면 그것은 시원합니다. 감사.
— Schotime

귀하의 의견을 다시 업데이트했습니다. 그러나 람다를 사용하여 문자열을 가져 와서 동적 LINQ를 사용하면 거꾸로 일하는 것처럼 보입니다. 람다를 사용하는 경우 람다를 사용하십시오 .-p 한 번에 전체 쿼리를 수행 할 필요가 없습니다. "일반 / 람다"OrderBy, "동적 LINQ / 문자열"등을 사용할 수 있습니다.
— Marc Gravell


4
모든 사람에게주의 사항 : 사용은 MemberExpression접근 만 얻을 여기에 나열된 이름 , 회원의를 하지 실제 얻을 수 MemberInfo때문에, 자체 MemberInfo시나리오 : 반환이 특정 "기본 dervied"에서 반사 형으로 보장 할 수 없습니다. lambda-expression-returning-expected-memberinfo를 참조하십시오 . 한 번 나에게 넘어졌다. 받아 들여진 대답도 이것으로 고통받습니다.
— nawfal

답변:


350

최근에 유형 안전 OnPropertyChanged 메서드를 만들기 위해 매우 비슷한 작업을 수행했습니다.

식에 대한 PropertyInfo 개체를 반환하는 메서드는 다음과 같습니다. 식이 속성이 아닌 경우 예외가 발생합니다.

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

source컴파일러가 메소드 호출에 타입 추론을 할 수 있도록 매개 변수가 사용됩니다. 당신은 다음을 할 수 있습니다

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

6
TSource에 대한 마지막 확인이 필요한 이유는 무엇입니까? 람다는 강력하게 입력되었으므로 필요하다고 생각하지 않습니다.
— HappyNomad

16
또한 2012 년 현재 소스 매개 변수 없이도 유형 유추가 올바르게 작동합니다.
— HappyNomad

4
@HappyNomad 세 번째 유형의 인스턴스 인 멤버를 가진 개체를 상상해보십시오. u => u.OtherType.OtherTypesProperty마지막 진술이 확인하는 경우를 만들 것입니다.
— joshperry

5
마지막 if 문은 if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))인터페이스도 허용 해야합니다 .
— Graham King

8
@GrayKing은 그다지 같지 if(!propInfo.ReflectedType.IsAssignableFrom(type))않습니까?
— Connell

192

소스와 속성을 강력하게 입력하고 람다에 대한 입력을 명시 적으로 유추하는 것이 다른 방법을 찾았습니다. 이것이 올바른 용어인지 확실하지 않지만 결과는 다음과 같습니다.

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

그런 다음 그렇게 부르십시오.

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

그리고 짜잔이 작동합니다.
모두 감사합니다.


4
이 솔루션은 약간 업데이트되어야합니다. 다음 기사를 확인하십시오-여기 링크가 있습니다
— Pavel Cermak

1
ASP.Net MVC를 수행하고 UI 계층 (HtmlHelper)에 대해서만 옵션입니다.
— Marc

3
C # 6.0부터 사용할 수 있습니다GetInfo(nameof(u.UserId))
— Vladislav

1
넷 코어에서 나는 이것을 사용해야했다 :var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
— Falk

146

나는 똑같은 일을하고 있었고 이것을 해결했습니다. 완전히 테스트되지는 않았지만 값 유형 관련 문제 (단일 표현 문제)

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
(에서 최근이 시도 또 다른 질문 , 그것은 하위 속성을 처리하지 않습니다 발견) : o => o.Thing1.Thing2반환 Thing2하지 Thing1.Thing2당신이 EntityFramework에서 사용하려는 경우 잘못된 포함하는
— drzaus

1
AKA (field.Body는 UnaryExpression입니까? ((UnaryExpression) field.Body) .Operand : field.Body) as 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;
}

멤버 및 단항 식을 처리합니다. 차이점은 UnaryExpression표현식이 값 유형을 MemberExpression나타내는 경우 a를 얻는 반면 표현식이 참조 유형을 나타내는 경우 는 얻는다는 것 입니다. 모든 것을 객체로 캐스트 할 수 있지만 값 유형은 상자로 묶어야합니다. 이것이 UnaryExpression이 존재하는 이유입니다. 참고.

가독성 (@Jowen)을 위해 다음과 같이 확장했습니다.

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, 가독성을 위해 <TField>를 생략했습니다. 문제가 있습니까? LambdaExpressions.GetName <Basket> (m => m.Quantity)
— Soren

1
@soren 나는 나보다 더 많은 누군가가 당신이 값 유형의 표현을 전달할 때 불필요한 boxing / unboxing의 가능성까지 코드를 열어 놓을 것을 제안 할 것이라고 확신하지만,이 방법으로 표현이 컴파일되고 평가되지 않기 때문에, 아마 문제가 아닙니다.
— 폴 플레밍

29

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

예:

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

이것은 struct / class / interface / delegate / array의 필드 / 속성 / 인덱서 ​​/ 메소드 / 확장 메소드 / 델리게이트의 문자열 이름을 얻기위한 일반적인 구현입니다. 정적 / 인스턴스 및 비 제네릭 / 제네릭 변형의 조합으로 테스트했습니다.

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

이 것도 간단한 while루프 로 작성할 수 있습니다 .

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

나는 재귀 접근 방식을 좋아하지만 두 번째 접근 방식은 더 읽기 쉽습니다. 다음과 같이 호출 할 수 있습니다.

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

마지막 멤버를 인쇄합니다.

노트 :

  1. 와 같은 체인 표현식의 경우 A.B.C"C"가 반환됩니다.

  2. consts, 배열 인덱서 또는 enums (모든 경우에 적용 할 수 없음) 에서는 작동하지 않습니다 .


19

Array.Length와 관련 하여 가장자리가 있습니다 . '길이'는 속성으로 노출되어 있지만 이전에 제안 된 솔루션에서는 사용할 수 없습니다.

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

이제 사용법 예 :

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

를 PropertyNameFromUnaryExpr확인하지 않으면 ArrayLength"someArray"가 콘솔에 인쇄됩니다 (컴파일러는 디버그에서도 특별한 경우로 최적화로서 백업 길이 필드에 직접 액세스하는 것으로 보입니다 ).


16

다음 은 Cameron이 제안한 방법에 대한 업데이트 입니다. 첫 번째 매개 변수는 필요하지 않습니다.

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

다음을 수행 할 수 있습니다.

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

확장 방법 :

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

당신은 할 수 있습니다 :

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

그는 u어떤 유형으로 추론 하지 않을 것입니다. 추론 할 유형이 없기 때문에 그렇게 할 수 없습니다. 당신이 할 수있는 일은GetPropertyInfo<SomeType>(u => u.UserID)
— Lucas

14

나는 제안 된 답변 중 일부가 MemberExpression/UnaryExpression / 하위 속성을 중첩 캡처하지 않습니다.

예) 오히려 o => o.Thing1.Thing2반환Thing1Thing1.Thing2 .

이 구별은 EntityFramework로 작업하려는 경우 중요합니다. DbSet.Include(...) .

파싱을 파싱하는 Expression.ToString()것이 잘 작동하고 비교적 빨리 작동 하는 것으로 나타났습니다 . 나는 그것을 UnaryExpression버전 과 비교 하고 심지어 그것이 더 빠른지 알아보기 위해 ToString벗어 Member/UnaryExpression났지만 그 차이는 무시할 만했다. 이것이 끔찍한 아이디어라면 저를 정정하십시오.

확장 방법

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

(구분 기호를 확인하면 너무 과도 할 수 있습니다)

데모 (LinqPad)

데모 + 비교 코드-https: //gist.github.com/zaus/6992590


1
+ 1 매우 흥미 롭습니다. 자신의 코드에서이 방법을 계속 사용 했습니까? 제대로 작동합니까? 에지 사례를 발견 한 적이 있습니까?
— Benjamin Gale

나는 당신의 생각을 보지 못합니다. 당신이 말한대로 당신 이 연결 한 답변으로가는 o => o.Thing1.Thing2반환되지 않습니다 . 실제로 귀하의 답변 은 원하거나 원치 않는 것을 반환 합니다. Thing1Thing2Thing1.Thing2
— nawfal

korman주의 사항과 함께 작동하지 않습니다 : stackoverflow.com/a/11006147/661933 . 해킹을 피하는 것이 좋습니다.
— nawfal

# 1 @nawfal 없습니다 - 원래 문제는 당신이다 싶어 Thing1.Thing2 , 결코 Thing1. 나는 말했다Thing2 술어의 요점 인의 가치 를 의미o.Thing1.Thing2 . 그 의도를 반영하여 답변을 업데이트하겠습니다.
— drzaus

@drzaus 죄송합니다. 아직 연락이 없습니다. 진정으로 이해하려고 노력합니다. 왜 여기에 다른 답변이 있다고 대답 Thing1하시겠습니까? 나는 그것이 전혀 재조정한다고 생각하지 않습니다.
— nawfal

6

C # 6 이전 프로젝트에는 확장 메소드를 사용하고 C # 6을 대상으로하는 프로젝트에는 nameof () 를 사용하고 있습니다.

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

그리고 나는 그것을 다음과 같이 부른다.

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

필드와 속성 모두에서 잘 작동합니다.


5

글쎄, 전화 할 필요 .Name.ToString()는 없지만 대체로 그것에 관한 것입니다. 필요한 유일한 고려 사항은 x.Foo.Bar"Foo", "Bar"또는 예외를 리턴 해야하는지 여부입니다. 즉, 반복해야합니다.

유연한 정렬에 대한 자세한 내용은 여기를 참조 하십시오 .


네 ... 정렬 열 링크를 생성하는 데 사용되는 첫 번째 수준의 것입니다. 예. 모델이 있고 정렬 기준으로 열 이름을 표시하려면 객체에 대한 강력한 형식의 링크를 사용하여 동적 linq에 소가없는 속성 이름을 얻을 수 있습니다. 건배.
— Schotime 2016 년

ToString단항 표현식에 대한 추악한 결과를 제공해야합니다.
— nawfal

3

기본 메소드는 문자열 만 허용하므로 ObjectStateEntry에서 확장 메소드를 작성하여 유형 안전 방식으로 수정 된 Entity Framework POCO 클래스의 특성을 플래그 할 수 있습니다. 속성에서 이름을 얻는 방법은 다음과 같습니다.

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

INotifyPropertyChanged아래 방법과 비슷한 구현 을 수행했습니다 . 여기서 속성은 아래 표시된 기본 클래스의 사전에 저장됩니다. 물론 상속을 사용하는 것이 항상 바람직하지는 않지만 뷰 모델의 경우 뷰 모델 클래스에서 허용되며 매우 깨끗한 속성 참조를 제공한다고 생각합니다.

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

다소 복잡한 기본 클래스가 아래에 나와 있습니다. 람다 식에서 속성 이름으로의 변환을 처리합니다. 이름 만 사용되므로 속성은 실제로 의사 속성입니다. 그러나 뷰 모델에 투명하게 보이며 뷰 모델의 속성에 대한 참조가 나타납니다.

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
당신은 기본적으로 재산 부대를 유지하고 있습니다. 나쁘지는 않지만 모델 클래스의 getter 및 setter에서 호출하는 것은 약간 쉽습니다 public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. 느릴 수 있지만 더 일반적이고 간단합니다.
— nawfal

실제로 간단한 종속성 속성 시스템을 구현하는 것은 어렵지만 실제로는 위의 구현보다 훨씬 성능이 뛰어납니다.
— Felix K.

3

이것은 또 다른 대답입니다.

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
ModelMetadataSystem.Web.Mvc네임 스페이스에 존재 합니다. 아마 일반적인 경우에 맞지 않을 수도 있습니다
— asakura89

3

다중 필드를 얻으려면이 기능을 종료하십시오.

/// <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
이것을 설명하겠습니까?

1

이 답변을 기반으로 PropertyInfo를 얻는 또 다른 방법 이 있습니다. 객체 인스턴스가 필요 없습니다.

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

다음과 같이 호출 할 수 있습니다.

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

1

입력 된 람다 식 에 대한 안전 검사를 포함하도록 @Cameron의 답변 을 업데이트했습니다 Convert.

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부터는 ExpressionVisitor속성을 찾는 데 사용할 수 있습니다.

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

이 방문자를 사용하는 방법은 다음과 같습니다.

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

이것은 최적 일 수 있습니다

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;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.