[C#] 람다 식에서 속성 이름 검색

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

예.

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



답변

최근에 유형 안전 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);


답변

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

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

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


답변

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

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


답변

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


답변

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


답변

이제 C # 6에서는 이와 같이 nameof 를 사용할 수 있습니다.nameof(User.UserId)

많은 이점이 있는데, 그중에는 런타임이 아니라 컴파일 타임에 수행된다는 것입니다 .

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


답변

이것은 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 (모든 경우에 적용 할 수 없음) 에서는 작동하지 않습니다 .