I have some (what I think are...) MemberExpression
's wrapped in lambas.
void Main()
{
Foo<Person>(x => x.Name, x => x.Id, x => x.Address);
}
void Foo<TSource>(params Expression<Func<TSource, TValue>>[] lambdas)
{
foreach (var lambda in lambdas)
{
Console.WriteLine(GetHierarchicalName(lambda));
}
}
string GetHierarchicalName<TSource, TValue>(Expression<Func<TSource, TValue>> lambda)
{
var member = lambda.Body as MemberExpression;
var hierarchy = new Stack<string>();
if (member == null)
{
throw new ArgumentException("You need to pass a lambda which references a member, silly!");
}
do
{
hierarchy.Push(member.Member.Name);
} while (member.Expression.NodeType == ExpressionType.MemberAccess && (member = member.Expression as MemberExpression) != null);
return String.Join("", hierarchy.ToArray());
}
My end goal is that Foo
will output "Name", "Id" and "Address" (additionally, when passing a lambda such as x => x.Foo.Bar.Baz
, "FooBarBaz" will be output).
However, at the moment I'm not specifying TValue
for Foo
; I can't, since each of the lambda's could return a different value... but I don't care, since all I need is the property path they're referencing.
I tried using object
in place of TValue
, but when a lambda returns an int
, the lambda passed to GetHierarchicalName
ends up being a Convert
, rather than a MemberExpression
.
How can I get around not specifying TValue
, such that I can pass arbitrary lambdas to Foo()
, and have it output the path to the member each lambda is referencing?
int
values need to be boxed to be represented as objects, that's why you're getting a Convert
expression. You have to get the Convert
expression's Operand
instead of Body
:
var member = lambda.Body as MemberExpression;
if (member == null && lambda.Body is UnaryExpression && lambda.Body.NodeType == ExpressionType.Convert)
{
member = (lambda.Body as UnaryExpression).Operand as MemberExpression;
}
You'll have to declare a bunch of overloads of the form Foo<TSource, T1, ..., TN>
, similar to how Action
and Func
themselves have overloads for up to 16 arguments. For example:
void Foo<TSource, T1, T2, T3>(Expression<Func<TSource, T1>> m1, Expression<Func<TSource, T2>> m2, Expression<Func<TSource, T3>> m3)
{
Console.WriteLine(GetHierarchicalName(m1));
Console.WriteLine(GetHierarchicalName(m2));
Console.WriteLine(GetHierarchicalName(m3));
}
This can then be called as:
Foo<string, int, string>(x => x.Name, x => x.Id, x => x.Address);
To get the compiler to infer types, Foo has to accept an extra argument of type TSource
:
Foo<TSource, T1, T2, T3>(TSource source, Expression<Func<TSource, T1>> m1, ...) { ... }
so it can be called as:
Foo(person, x => x.Name, x => x.Id, x => x.Address);
But all of that is a lot of work for little gain.
A conversion is represented by a UnaryExpression
with NodeType ExpressionType.Convert
. In this case, its Operand
property contains the MemberExpression
you're looking for.
You need some code that can code with any expression, you don't need to specify it is a typed expression so you can just use the base Expression class.
Here is some code from the utilities class I use, it has some code to handle the Converts and strip them out. Note I wrote this in LinqPad, so you may need to replace Dump() with something like Console.WriteLine if you want to run it elsewhere.
void Main()
{
Foo(x => x.Name, x => x.Id, x => x.Address);
}
void Foo(params Expression<Func<Bar, object>>[] lambdas)
{
foreach (var lambda in lambdas)
{
ExpressionToString(lambda).Dump();
}
}
public static string ExpressionToString(Expression selector)
{
string left, right, result;
switch (selector.NodeType)
{
case ExpressionType.MemberAccess:
var memberExpression = selector as MemberExpression;
right = (memberExpression.Member as PropertyInfo).Name;
if (memberExpression.Expression.NodeType == ExpressionType.MemberAccess)
{
left = ExpressionToString(memberExpression.Expression);
result = left + right;
}
else
{
result = right;
}
break;
case ExpressionType.Call:
var method = selector as MethodCallExpression;
left = ExpressionToString(method.Arguments[0]);
right = ExpressionToString(method.Arguments[1]);
result = left + right;
break;
case ExpressionType.Lambda:
var lambda = selector as LambdaExpression;
result = ExpressionToString(lambda.Body);
break;
case ExpressionType.Quote:
case ExpressionType.Convert:
var unary = selector as UnaryExpression;
result = ExpressionToString(unary.Operand);
break;
default:
throw new InvalidOperationException("Expression must be MemberAccess, Call, Quote, Convert or Lambda");
}
return result;
}
public class Bar
{
public String Name { get; set; }
public String Id { get; set; }
public String Address { get; set; }
}
This will produce:
Name
Id
Address
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.