我有一些(我认为......)MemberExpression
包裹在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());
}
我的最终目标是Foo
将输出“Name”,“Id”和“Address”(另外,当传递lambda如x => x.Foo.Bar.Baz
时,将输出“FooBarBaz”。) / p>
但是,目前我没有为TValue
指定Foo
;我不能,因为每个lambda都可以返回不同的值... 但是我不在乎,因为我需要的只是他们引用的属性路径。
我尝试使用object
代替TValue
,但当lambda返回int
时,传递给GetHierarchicalName
的lambda最终成为Convert
而不是MemberExpression
。
我怎样才能不指定TValue
,这样我可以将任意lambda传递给Foo()
,并让它输出每个lambda引用的成员的路径?
答案 0 :(得分:2)
int
值需要加框以表示为对象,这就是您获得Convert
表达式的原因。您必须获取Convert
表达式Operand
而不是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;
}
答案 1 :(得分:1)
您必须声明Foo<TSource, T1, ..., TN>
形式的一堆重载,类似于Action
和Func
本身有多达16个参数的重载。例如:
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));
}
然后可以将其命名为:
Foo<string, int, string>(x => x.Name, x => x.Id, x => x.Address);
为了让编译器推断类型,Foo必须接受TSource
类型的额外参数:
Foo<TSource, T1, T2, T3>(TSource source, Expression<Func<TSource, T1>> m1, ...) { ... }
所以它可以被称为:
Foo(person, x => x.Name, x => x.Id, x => x.Address);
但所有这些都是很多工作,收获甚微。
转化由带有NodeType UnaryExpression
的{{1}}表示。在这种情况下,其ExpressionType.Convert
属性包含您要查找的Operand
。
答案 2 :(得分:0)
您需要一些可以使用任何表达式编码的代码,您不需要指定它是一个类型化的表达式,因此您只需使用基本的Expression类。
以下是我使用的实用程序类中的一些代码,它有一些代码来处理转换并将它们删除。注意我在LinqPad中写了这个,所以如果你想在其他地方运行它,你可能需要用Console.WriteLine替换Dump()。
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; }
}
这将产生:
Name
Id
Address