问题
I have a method which i want to convert to Extension Method
public static string GetMemberName<T>(Expression<Func<T>> item)
{
return ((MemberExpression)item.Body).Member.Name;
}
and calling it like
string str = myclass.GetMemberName(() => new Foo().Bar);
so it evaluates to str = "Bar"; // It gives the Member name and not its value
Now when i try to convert this to extension method by this
public static string GetMemberName<T>(this Expression<Func<T>> item)
{
return ((MemberExpression)item.Body).Member.Name;
}
and call it like
string str = (() => new Foo().Bar).GetMemberName();
Error says Operator '.' cannot be applied to operand of type 'lambda expression'
Where am I wrong?
回答1:
There are really two things here, first, passing () => new Foo().Bar
into the method that accepts Expression<Func<T>>
treats the specified expression tree as a Expression<Func<T>>
, but () => new Foo().Bar
is not an Expression<Func<T>>
on its own.
Second, in order to get your extension method to accept any lambda (such as you're supplying), you'd have to use the type that corresponds to any expression tree. But, as you may have already guessed based on the message ... to operand of type 'lambda expression'
where you'd usually see the name of the type inside the quotes, that lambda expressions are treated specially by the language, making what you're trying to do, without casting first, impossible.
The way to invoke your extension method in extension method form would be (in the case that Bar
is of type string
)
((Expression<Func<string>>)(() => new Foo().Bar)).GetMemberName()`
which doesn't seem like it would be all that desirable.
回答2:
Where am I wrong?
The compiler is telling you exactly what's wrong - you can't use .
on a lambda expression.
The lambda expression doesn't have any particular type - it's just convertible to the expression tree.
A member-access expression (which is what you're trying to do) is only available in the forms
primary-expression . identifier type-argument-list(opt)
predefined-type . identifier type-argument-list(opt)
qualified-alias-member . identifier type-argument-list(opt)
... and a lambda expression isn't a primary expression.
Interestingly, this argument doesn't hold for an anonymous method expression, but for you still can't use a member access expression on that, either. Section 7.6.4 of the C# spec lists how a member access expression is bound, and the bulk of the options are either under "If E is a predefined-type or a primary-expression classified as a type" (which doesn't apply to anonymous methods) or "If E is a property access, variable, or value, the type of which is T" - but an anonymous method is an anonymous function, and as per section 7.15: "An anonymous function does not have a value or type in and of itself".
EDIT: You can still use extension methods on expression trees, you just can't use them directly on lambda expressions. So this will work:
Expression<Func<int>> expr = () => new Foo().Bar;
string name = expr.GetMemberName();
... but it's obviously not as useful. (Ditto with a cast as per mlorbetske's answer.)
回答3:
To get typed expression, you will have to write it out. As others have said, there is no way compiler will automatically infer it from a lambda expression since a lambda expression can mean two things - either a delegate or an expression tree.
You can get the expression relatively simpler, by letting the compiler infer the type for you partially, like (from this answer):
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
Call it like:
var expr1 = Lambda.Expression(() => new Foo().Bar);
var expr2 = Lambda<string>.Expression(x => x.Length); //etc
Your options are:
Cast backward, to exact expression type and then call extension method on it
var name = ((Expression<Func<BarType>>)(() => new Foo().Bar)).GetMemberName();
looks ugly - cure worse than cause.
Get the expression first into a variable
Expression<Func<BarType>> expr = () => new Foo().Bar; var name = expr.GetMemberName();
Slightly better, but still looks little off for a trivial thing.
Using the
Lambda
classes written abovevar name = Lambda.Expression(() => new Foo().Bar).GetMemberName();
Even better. It's just a little less typing.
Your first pattern, which I think is the best you can get as far as readability goes
I dont think you can improve upon that considering C# rules related to lambda expressions. That said I think few improvements can be made.
First, extend the functionality to other lambda expression types. You would need more than
Func<T>
types to handle all cases. Decide what are the expression types you have to handle. For instance if you have aFunc<S, T>
type (as in your question -Bar
onFoo
), it looks better. Compare thismyclass.GetMemberName(() => new Foo().Bar);
with
myclass.GetMemberName<Foo>(x => x.Bar);
I would say these overloads would do:
//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);
Now these can be used not just in the cases mentioned in comments, surely there are overlapping scenarios. For instance, if you already have instance of
Foo
, then its easier to call the second overload (Func<T>
) overload for name of propertyBar
, likemyclass.GetMemberName(() => foo.Bar)
.Secondly, implement a
GetMemberName
functionality common to all these overloads. An extension method onExpression<T>
orLambdaExpression
would do. I prefer the latter so that you get to call it even in non-strongly typed scenarios. I would write it like this, from this answer: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); }
Lastly,
GetMemberName
is not a good name if you're to call it non-extension way.expression.GetMemberName()
sounds more logical.Member.NameFrom<int>(x => x.ToString())
orMemberName.From<string>(x => x.Length)
etc are more descriptive names for static calls.So overall the class might look like:
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(); } }
And usage:
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);
Most concise and clean.
For properties and fields, it can be transformed to an anonymous class and then using reflection the member name can be read, as shown here.
public static string GetMemberName<T>(T item) where T : class { if (item == null) return null; return typeof(T).GetProperties()[0].Name; }
Call it like
var name = GetMemberName(new { new Foo().Bar });
It's faster, but has certain quirks, like not very refactor friendly, and doesnt help in case of methods as members. See the thread..
Of all I prefer 4.
来源:https://stackoverflow.com/questions/11167676/error-message-operator-cannot-be-applied-to-operand-of-type-lambda-express