错误消息“操作员”。 在将方法转换为扩展方法时,不能应用于’lambda expression’类型的操作数?

我有一个方法,我想转换为扩展方法

public static string GetMemberName(Expression<Func> 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 str = "Bar"; // It gives the Member name and not its value

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

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

并称之为

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

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

我哪里错了?

这里有两件事,第一,传递() => new Foo().Bar接受Expression>将指定的表达式树视为Expression> ,但是() => new Foo().Bar本身不是Expression>

其次,为了让你的扩展方法接受任何lambda(例如你提供的),你必须使用与任何表达式树相对应的类型。 但是,正如你可能已经猜到的那样,基于消息... to operand of type 'lambda expression' ,你通常会在引号中看到类型的名称,lambda表达式是由语言专门处理的,你想要做的事情,不先铸造,不可能。

以扩展方法forms调用扩展方法的方法是(在Barstring类型的情况下)

 ((Expression>)(() => new Foo().Bar)).GetMemberName()` 

这似乎并不是那么令人满意。

我哪里错了?

编译器正在告诉你究竟出了什么问题 – 你无法使用. 在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> expr = () => new Foo().Bar; string name = expr.GetMemberName(); 

……但它显然不那么有用。 (根据mlorbetske的回答,与演员同上。)

要获得类型化表达式,您必须将其写出来。 正如其他人所说,编译器无法从lambda表达式自动推断它,因为lambda表达式可能意味着两件事 – 委托或表达式树。

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

 public sealed class Lambda { public static Func Func(Func func) { return func; } public static Expression> Expression(Expression> expression) { return expression; } } public sealed class Lambda { public static Func Func(Func func) { return func; } public static Expression> Expression(Expression> expression) { return expression; } } //etc, to cover more cases 

称之为:

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

你的选择是:

  1. 向后投射到精确的表达式类型,然后调用它的扩展方法

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

    看起来丑陋 – 治愈比原因更糟糕。

  2. 首先将表达式放入变量中

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

    稍微好一点,但仍然看起来很少有一件小事。

  3. 使用上面写的Lambda

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

    更好。 这只是一点点打字。

  4. 你的第一个模式,我认为这是你可以获得的最好的可读性

    考虑到与lambda表达式相关的C#规则,我认为你不能改进。 也就是说,我认为可以做出一些改进。

    首先,将function扩展到其他lambda表达式类型。 您需要的不仅仅是Func类型来处理所有情况。 确定您必须处理的表达式类型。 例如,如果你有一个Func类型(如你的问题 – Foo Bar ),它看起来更好。 比较一下

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

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

    我会说这些重载会做:

     //for static methods which return void public static string GetMemberName(Expression expr); //for static methods which return non-void and properties and fields public static string GetMemberName(Expression> expr); //for instance methods which return void public static string GetMemberName(Expression> expr); //for instance methods which return non-void and properties and fields public static string GetMemberName(Expression> expr); 

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

    其次,实现所有这些重载共有的GetMemberNamefunction。 ExpressionLambdaExpression的扩展方法可以。 我更喜欢后者,所以你甚至可以在非强类型场景中调用它。 我会这样写, 从这个回答 :

     public static string GetMemberName(this LambdaExpression memberSelector) { Func 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(x => x.ToString())MemberName.From(x => x.Length)等是静态调用的更具描述性的名称。

    所以整体课程可能如下:

     public static class Member { public static string NameFrom(Expression expr) { return expr.GetMemberName(); } public static string NameFrom(Expression> expr) { return expr.GetMemberName(); } public static string NameFrom(Expression> expr) { return expr.GetMemberName(); } public static string NameFrom(Expression> expr) { return expr.GetMemberName(); } } 

    用法:

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

    最简洁干净。

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

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

    称之为

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

    它更快,但有一些怪癖,如不是非常重构友好,并且在方法作为成员的情况下没有帮助。 看线程..

我更喜欢4。