10

我有一个方法要转换为扩展方法

public static string GetMemberName<T>(Expression<Func<T>> item)
{
    return ((MemberExpression)item.Body).Member.Name;
}

并称它为

string str = myclass.GetMemberName(() => new Foo().Bar); 

所以它评估为str = "Bar"; // It gives the Member name and not its value

现在,当我尝试通过此将其转换为扩展方法时

public static string GetMemberName<T>(this Expression<Func<T>> item)
{
    return ((MemberExpression)item.Body).Member.Name;
}

并称它为

string str = (() => new Foo().Bar).GetMemberName();

错误说Operator '.' cannot be applied to operand of type 'lambda expression'

我哪里错了?

4

3 回答 3

8

我哪里错了?

编译器准确地告诉你出了什么问题——你不能.在 lambda 表达式上使用。

lambda 表达式没有任何特定类型 - 它只是可转换为表达式树。

成员访问表达式(这是您尝试做的)仅在表单中可用

primary-expression . identifier type-argument-list(opt)
predefined-type . identifier type-argument-list(opt)
qualified-alias-member . identifier type-argument-list(opt)

...并且 lambda 表达式不是主要表达式。

有趣的是,此参数不适用于匿名方法表达式,但您仍然不能在其上使用成员访问表达式。C# 规范的第 7.6.4 节列出了如何绑定成员访问表达式,并且大部分选项在“如果 E 是预定义类型或归类为类型的主表达式”下(不适用到匿名方法)或“如果 E 是属性访问、变量或值,其类型为 T” - 但匿名方法是匿名函数,并且根据第 7.15 节:“匿名函数没有值或自行输入”。

编辑:您仍然可以在表达式树上使用扩展方法,只是不能直接在 lambda 表达式上使用它们。所以这将起作用:

Expression<Func<int>> expr = () => new Foo().Bar;
string name = expr.GetMemberName();

...但它显然没有那么有用。(根据 mlorbetske 的回答同上。)

于 2012-06-23T07:30:10.440 回答
8

这里实际上有两件事,首先,传递() => new Foo().Bar给接受的方法Expression<Func<T>>将指定的表达式树视为 a Expression<Func<T>>,但它本身() => new Foo().Bar不是 a Expression<Func<T>>

其次,为了让您的扩展方法接受任何 lambda(例如您提供的),您必须使用与任何表达式树相对应的类型。但是,您可能已经根据... to operand of type 'lambda expression'通常在引号内看到类型名称的消息猜到了,lambda 表达式由语言特殊处理,从而使您尝试做的事情,而无需先进行转换,不可能的。

以扩展方法形式调用扩展方法的方法是(在Bar类型为的情况下string

((Expression<Func<string>>)(() => new Foo().Bar)).GetMemberName()` 

这似乎并不是那么理想。

于 2012-06-23T07:33:03.690 回答
2

要获得键入的表达式,您必须将其写出来。正如其他人所说,编译器不会自动从 lambda 表达式推断它,因为 lambda 表达式可能意味着两件事——委托或表达式树。

通过让编译器为您部分推断类型,您可以使表达式相对简单,例如(来自此答案):

public sealed class Lambda
{
    public static Func<T> Func<T>(Func<T> func)
    {
        return func;
    }

    public static Expression<Func<T>> Expression<T>(Expression<Func<T>> expression)
    {
        return expression;
    }
}

public sealed class Lambda<S>
{
    public static Func<S, T> Func<T>(Func<S, T> func)
    {
        return func;
    }

    public static Expression<Func<S, T>> Expression<T>(Expression<Func<S, T>> expression)
    {
        return expression;
    }
}

//etc, to cover more cases

像这样称呼它:

var expr1 = Lambda.Expression(() => new Foo().Bar);
var expr2 = Lambda<string>.Expression(x => x.Length); //etc

您的选择是:

  1. 向后转换,精确到表达式类型,然后在其上调用扩展方法

    var name = ((Expression<Func<BarType>>)(() => new Foo().Bar)).GetMemberName();
    

    看起来丑陋 - 治疗比病因更糟糕。

  2. 首先将表达式获取到变量中

    Expression<Func<BarType>> expr = () => new Foo().Bar;
    var name = expr.GetMemberName();
    

    稍微好一点,但对于一件微不足道的事情来说仍然看起来没什么。

  3. 使用Lambda上面写的类

    var name = Lambda.Expression(() => new Foo().Bar).GetMemberName();
    

    甚至更好。只是打字少了一点。

  4. 你的第一个模式,我认为就可读性而言,这是你能得到的最好的模式

    考虑到与 lambda 表达式相关的 C# 规则,我认为您无法改进这一点。也就是说,我认为几乎没有什么可以改进的。

    首先,将功能扩展到其他 lambda 表达式类型。您需要的不仅仅是Func<T>类型来处理所有情况。确定您必须处理的表达式类型。例如,如果您有一个Func<S, T>类型(如您的问题 - Baron Foo),它看起来会更好。比较这个

    myclass.GetMemberName(() => new Foo().Bar);
    

    myclass.GetMemberName<Foo>(x => x.Bar);
    

    我会说这些重载会:

    //for static methods which return void
    public static string GetMemberName(Expression<Action> expr);
    
    //for static methods which return non-void and properties and fields
    public static string GetMemberName<T>(Expression<Func<T>> expr);
    
    //for instance methods which return void
    public static string GetMemberName<T>(Expression<Action<T>> expr);
    
    //for instance methods which return non-void and properties and fields
    public static string GetMemberName<S, T>(Expression<Func<S, T>> expr);
    

    现在这些不仅可以在评论中提到的情况下使用,肯定还有重叠的场景。例如,如果您已经有 的实例Foo,则更容易调用Func<T>属性名称的第二个重载 () 重载Bar,例如myclass.GetMemberName(() => foo.Bar).

    其次,实现GetMemberName所有这些重载共有的功能。Expression<T>LambdaExpression将做的扩展方法。我更喜欢后者,这样即使在非强类型场景中你也可以调用它。我会这样写,从这个答案

    public static string GetMemberName(this LambdaExpression memberSelector)
    {
        Func<Expression, string> nameSelector = null;
        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);
    }
    

    最后,GetMemberName如果您将其称为非扩展方式,这不是一个好名字。expression.GetMemberName()听起来更合乎逻辑。Member.NameFrom<int>(x => x.ToString())or MemberName.From<string>(x => x.Length)etc 是静态调用的更具描述性的名称。

    所以总的来说这个类可能看起来像:

    public static class Member
    {
        public static string NameFrom(Expression<Action> expr)
        {
            return expr.GetMemberName();
        }
    
        public static string NameFrom<T>(Expression<Func<T>> expr)
        {
            return expr.GetMemberName();
        }
    
        public static string NameFrom<T>(Expression<Action<T>> expr)
        {
            return expr.GetMemberName();
        }
    
        public static string NameFrom<T>(Expression<Func<T, object>> expr)
        {
            return expr.GetMemberName();
        }
    }
    

    和用法:

    var name1 = Member.NameFrom(() => Console.WriteLine());
    var name2 = Member.NameFrom(() => Environment.ExitCode);
    var name3 = Member.NameFrom<Control>(x => x.Invoke(null));
    var name4 = Member.NameFrom<string>(x => x.Length);
    

    最简洁干净。

  5. 对于属性和字段,可以将其转换为匿名类,然后使用反射读取成员名称,如下所示。

    public static string GetMemberName<T>(T item) where T : class
    {
        if (item == null)
            return null;
    
        return typeof(T).GetProperties()[0].Name;
    }
    

    像这样称呼它

    var name = GetMemberName(new { new Foo().Bar });
    

    它更快,但有一些怪癖,比如对重构不太友好,并且在方法作为成员的情况下没有帮助。看题。。

其中我更喜欢4。

于 2013-04-27T01:17:34.997 回答