453

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

예.

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

속성이 문자열 일 때만 memberexpression으로 캐스팅했습니다. 모든 속성이 문자열이 아니기 때문에 객체를 사용해야했지만 그 대신 단항식을 반환합니다.

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


  • 더 좋은 코드처럼? 그렇게 생각하지 않아요. typechecking은 전체 표현식으로 만 확장되므로 런타임에 실제로 검사해야합니다. :( - MichaelGG
  • 그래, 좀 더 나은 방법이 있을지 궁금해하는구나. 나 한테 좀 해킹 된 것 같아. 하지만 그게 시원하면. 감사. - Schotime
  • 나는 당신의 코멘트를 갱신했다. 하지만 람다를 사용하여 동적 인 LINQ를 사용할 수 있도록 문자열을 얻습니다. 거꾸로하는 일로 나를 때립니다 ... lambda를 사용한다면, lambda -p를 사용하십시오. 하나의 전체 쿼리를 수행 할 필요가 없습니다 단계 - " 정규 / 람다 "를 사용할 수 있습니다. OrderBy, "동적 LINQ / 문자열" 어디 등 - Marc Gravell
  • 가능한 중복get-property-name-and-type-using-lambda-expression - nawfal
  • 모든 사람에게 보내는 메모 :MemberExpression여기에 나열된 접근 방식은이름회원의아니실제를 얻으려면MemberInfo그 자체, 왜냐하면MemberInfo돌려 주어지는 것은, 특정의 "dervied : base"의 리후 레크 트 된 형태가되는 것이 보증되지 않습니다. 시나리오. 만나다lambda-expression-not-returning-expected-memberinfo. 한 번 날 속였다. 받아 들여진 응답은 이것으로 너무 고통받습니다. - nawfal

19 답변


314

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

다음은 표현식에 대한 PropertyInfo 객체를 반환하는 메소드입니다. 표현식이 속성이 아닌 경우 예외를 throw합니다.

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


  • 거기에 TSource에 관한 마지막 점검이있는 이유는 무엇입니까? 람다는 강력하게 입력되었으므로 람다는 필요하다고 생각하지 않습니다. - HappyNomad
  • 또한 2012 년 현재 소스 매개 변수없이 형식 유추가 올바르게 작동합니다. - HappyNomad
  • 마지막 if 문은 다음과 같아야합니다.if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))인터페이스도 허용합니다. - Graham King
  • @GrayKing은 그냥 똑같을 수 없다.if(!propInfo.ReflectedType.IsAssignableFrom(type))? - Connell
  • 반사 (의도 한 말장난)에 네, 맞습니다. - Graham King

167

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

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

그리고 그것은 작동합니다.

모두에게 감사드립니다.


  • 이 솔루션은 조금 업데이트해야합니다. 다음 문서를 확인하십시오 - 여기에링크 - Pavel Cermak
  • ASP.Net MVC를 수행하고 UI 계층 (HtmlHelper)에만 해당하는 옵션입니다. - Marc
  • C #6.0부터 사용할 수 있습니다.GetInfo(nameof(u.UserId)) - Vladislav

137

나는 같은 일로 놀고 있었고 이것을 해결했다. 완전히 테스트되지는 않았지만 값 유형 (이 문제를 다루는 단항식 문제)을 처리하는 것처럼 보입니다.

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


  • 단항 조건 잡기 좋은 일 ... +1 - Gabe
  • 최근에 시도했습니다 (에서다른 질문), 하위 속성을 처리하지 않는다는 것을 알게되었습니다.o => o.Thing1.Thing2돌아올거야.Thing2, 아니라Thing1.Thing2, EntityFramework에서 사용하려는 경우 올바르지 않습니다. - drzaus
  • 후속 답변 참조 -stackoverflow.com/a/17220748/1037948 - drzaus
  • +1 : 이것은 나를 위해 일했으며 내가 필요한 가장 간단한 해결책이었습니다. 게시 해 주셔서 감사합니다. - fourpastmidnight
  • AKA (field.Body는 UnaryExpression? ((UnaryExpression) field.Body) .Operand : field.Body)를 MemberExpression으로 사용합니다. - user3638471

47

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표현식이 참조 유형을 나타내는 경우 모든 것은 객체에 캐스트 될 수 있지만 값 유형은 박스 처리되어야합니다. 이것이 단항 표현이 존재하는 이유입니다.참고.

가독성을 위해 (@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
  • @soren 나보다 더 잘 조정 된 누군가가 값 유형의 표현식을 전달할 때 불필요한 권투 / 언 박싱의 가능성까지 코드를 열 것을 제안 할 수 있습니다. 표현식이 컴파일되고 평가되지 않기 때문입니다 이 방법에서는 문제가되지 않습니다. - Paul Fleming

19

가장 중요한 경우는Array.길이. '길이'는 속성으로 표시되지만 이전에 제안 된 솔루션에서는 사용할 수 없습니다.

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"는 콘솔에 출력 될 것입니다 (컴파일러는 백킹 길이에 대한 직접 액세스를 생성하는 것으로 보입니다, 최적화, 심지어는 디버그에서, 따라서 특별한 경우).


18

struct / class / interface / delegate / array의 fields / properties / indexers / methods / extension methods / delegate의 문자열 이름을 가져 오기위한 일반적인 구현입니다. 나는 static / instance와 non-generic / generic 변형의 조합으로 테스트했다.

//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. 이것은 다음과 함께 작동하지 않습니다.const배열 인덱서 또는enums (모든 경우를 커버하는 것은 불가능 함).


16

여기에 대한 업데이트가 있습니다.카메론이 제안한 방법. 첫 x 째 매개 변수는 필요하지 않습니다.

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
  • 그게 맞아요, 응답이 감사합니다. - Adrian

15

지금 C #6에서 당신은 간단하게 사용할 수 있습니다.의 이름이렇게nameof(User.UserId)

많은 이점을 가지고 있는데, 그 중에는컴파일 시간런타임이 아닙니다.

https://msdn.microsoft.com/en-us/magazine/dn802602.aspx


13

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

전의)o => o.Thing1.Thing2보고Thing1오히려Thing1.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 매우 흥미 롭습니다. 코드에서이 메서드를 계속 사용 했습니까? 괜찮습니까? 가장자리 사건을 발견 했니? - Benjamin Gale
  • @ 벤자민 지금까지 너무 좋아. - drzaus
  • 나는 너의 생각을 보지 못한다. 연결된 답변으로 이동하기o => o.Thing1.Thing2돌아 오지 않았습니다.Thing1네가 말한대로Thing2. 사실 당신의 대답은 다음과 같습니다.Thing1.Thing2수도 있고 그렇지 않을 수도 있습니다. - nawfal
  • 케이스 코만주의와 관련하여주의를 기울이지 마십시오.stackoverflow.com/a/11006147/661933. 항상 해킹을 피하는 것이 좋습니다. - nawfal
  • @nawfal #1 - 원래 문제는필요 Thing1.Thing2결코Thing1. 나는 말했다Thing2의미하는o.Thing1.Thing2이것은 술어의 요점입니다. 그 의도를 반영하도록 답변을 업데이트하겠습니다. - drzaus

5

음, 전화 할 필요가 없습니다..Name.ToString(), 넓게는 그것에 관한 것입니다, 그렇습니다. 필요한 유일한 고려 사항은x.Foo.Bar"Foo", "Bar"또는 예외를 반환해야합니다. 즉, 반복 할 필요가 있습니다.

유연한 정렬에 대한 자세한 내용은 (re comment)을 참조하십시오.이리.


  • 네 ... 정렬 열 링크를 생성하는 데 사용되는 유일한 첫 번째 레벨 것입니다. 예. 모델이 있고 정렬 할 열 이름을 표시하려면 객체에 대한 강력한 형식의 링크를 사용하여 동적 linq에서 암소가없는 속성 이름을 가져올 수 있습니다. 건배. - Schotime
  • ToString단항 식에 대해 추악한 결과를 제공해야합니다. - nawfal

5

나는 C #6 이전 프로젝트를위한 확장 메소드를 사용하고있다.의 이름()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;
    }
}

그리고 저는 그것을 다음과 같이 부릅니다 :

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

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


3

기본 메서드는 문자열 만 허용하기 때문에 형식 안전 방식으로 수정 된대로 Entity Framework POCO 클래스의 속성에 플래그를 지정할 수 있도록 ObjectStateEntry에 대한 확장 메서드를 만들었습니다. 속성에서 이름을 얻는 내 방식은 다음과 같습니다.

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


  • 기본적으로 속성 가방을 유지 관리하고 있습니다. 나쁘지는 않지만 모델 클래스의 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;
    }


  • ModelMetadata에 존재하다System.Web.Mvc네임 스페이스. 일반적인 경우에는 적합하지 않을 수 있습니다. - asakura89

2

배수 필드를 가져 오려면이 함수를 그대로 두십시오.

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



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)
{
    // https://stackoverflow.com/questions/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

나는 업데이트했다.@ 카메론의 대답에 대한 몇 가지 안전 점검을 포함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.");
}

연결된 질문


관련된 질문

최근 질문