无法将类型为'System.Linq.Expressions.UnaryExpression'的对象强制转换为类型'System.Linq.Expressions.MemberExpression'

43

我在C#中创建了一个方法,用于获取方法名称

public string GetCorrectPropertyName<T>(Expression<Func<T, string>> expression)
{
   return ((MemberExpression)expression.Body).Member.Name; // Failure Point
}

并将其称为

string lcl_name = false;
public string Name
{
get { return lcl_name ; }
set 
    {
        lcl_name = value;
        OnPropertyChanged(GetCorrectPropertyName<ThisClassName>(x => x.Name));
}
}

如果属性是字符串,则代码可以正常运行,但对于所有其他类型,则会出现以下异常:

无法将类型为“System.Linq.Expressions.UnaryExpression”的对象强制转换为类型“System.Linq.Expressions.MemberExpression”。

  1. 我已将方法签名中的字符串更改为对象,但它仍然失败了。
  2. 我将调用从 x => x.PropertyName 更改为 x => Convert.ToString(x.PropertyName),但它仍然失败了。

我错在哪里了?


2
在我看来,最好使用一种变体,其中助手接受一个 Expression<Func<T>>。这会将调用站点语法更改为 GetCorrectPropertyName(() => this.Name),在我看来,这既更易于输入(无需提供泛型类型参数),也更易于阅读(this.Name非常明确地传达了意图)。 - Jon
@Jon:伙计,随便加上你的答案。如果比当前答案更好,我一定会接受你的答案。 - Nikhil Agrawal
我不想这样做,因为这会篡改你问题的意图。但是你可以从这里轻松获取它的代码,微软在Prism中也正是这样做的。 - Jon
在Prism 2中,没有任何阻止你这样做的东西(或者完全不使用Prism也可以)。 - Jon
@Jon:但是这种棱镜的方法只适用于属性,还是可以应用于方法呢? - Nikhil Agrawal
显示剩余2条评论
5个回答

71
您需要单独一行来提取输入表达式为一元表达式的成员。
这是从VB.Net转换过来的,可能有些偏差 - 如果需要进行微调,请告诉我。
public string GetCorrectPropertyName<T>(Expression<Func<T, Object>> expression)
{
    if (expression.Body is MemberExpression) {
        return ((MemberExpression)expression.Body).Member.Name;
    }
    else {
        var op = ((UnaryExpression)expression.Body).Operand;
        return ((MemberExpression)op).Member.Name;
    }                
}

VB版本为:

Public Shared Function GetCorrectPropertyName(Of T) _
             (ByVal expression As Expression(Of Func(Of T, Object))) As String
    If TypeOf expression.Body Is MemberExpression Then
        Return DirectCast(expression.Body, MemberExpression).Member.Name
    Else
        Dim op = (CType(expression.Body, UnaryExpression).Operand)
        Return DirectCast(op, MemberExpression).Member.Name
    End If
End Function

请注意,输入表达式不一定返回字符串 - 这限制了您只能读取返回字符串的属性。

4
人们不禁想知道为什么像这样的东西还没有被包含在BCL或扩展中。它非常有用。 - BoltClock
只是想知道如何使用/调用它的属性和方法(VB.Net的Sub和Function)? - Nikhil Agrawal
@NikhilAgrawal:最好提出一个新问题,参考这个问题,并充分解释您的要求。 - Jon Egerton
我想要一个扩展方法,它可以返回属性或方法(VB.Net的Sub和Function)的名称。该属性/方法可以与调用它的位置相同,也可以不同。 - Nikhil Agrawal
这非常有用!谢谢! - Ben Pretorius

15
这似乎与装箱/拆箱有关。返回需要装箱的值类型的 Lambda 表达式将表示为一元表达式,而返回引用类型的 Lambda 表达式将表示为成员表达式。

4
这进一步阐述了最高评分答案,并为那些不仅满足于答案的人提供更深入的见解。 - gregsonian

4

在我提出这个问题之后(是的,我就是OP),我收到了Jon的评论。

然后我想到了这个。

public string ResolvePropertyName<TEntity>(Expression<Func<TEntity>> expression)
{
try {
    if (expression == null) {
        Throw New ArgumentNullException("propertyExpression")
    }

    object memberExpression = expression.Body as MemberExpression;
    if (memberExpression == null) {
        Throw New ArgumentException("The expression is not a member access expression.", "propertyExpression")
    }

    object property = memberExpression.Member as PropertyInfo;
    if (property == null) {
        Throw New ArgumentException("The member access expression does not access a property.", "propertyExpression")
    }

    object getMethod = property.GetGetMethod(true);
    if (getMethod.IsStatic) {
        Throw New ArgumentException("The referenced property is a static property.", "propertyExpression")
    }
    return memberExpression.Member.Name;
} catch (Exception ex) {
    return string.Empty;
}
}

2
上面的答案的现代版本。
private static string GetPropertyName<T>(Expression<Func<T, object>> expression) 
=> expression.Body switch
{
    MemberExpression expr => expr.Member.Name,
    UnaryExpression expr => ((MemberExpression)expr.Operand).Member.Name,
    _ => throw new ArgumentException($"Argument {nameof(expression)} is not a property expression.", nameof(expression)),
};

0

如果你需要处理条件表达式,请添加以下内容:

else if (expression.Body is ConditionalExpression expr)
{
    return ((MemberExpression)((bool)(System.Linq.Expressions.Expression.Lambda(expr.Test).Compile().DynamicInvoke())
        ? expr.IfTrue
        : expr.IfFalse)).Member.Name;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接