람다 식에서 속성 이름 검색


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.