如何将任意 lambda 作为参数传递给函数(以获取命名层次结构)

本文关键字:获取 层次结构 函数 任意 lambda 参数传递 | 更新日期: 2023-09-27 18:30:33

我有一些(我认为是... MemberExpression被兰巴斯包裹着。

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"(此外,当传递诸如x => x.Foo.Bar.Baz之类的lambda时,将输出"FooBarBaz")。

但是,目前我没有指定Foo TValue;我不能,因为每个 lambda 都可以返回不同的值......我不在乎,因为我所需要的只是他们引用的属性路径。

我尝试用object代替TValue,但是当一个lambda返回一个int时,传递给GetHierarchicalName的lambda最终会成为一个Convert,而不是一个MemberExpression

我怎样才能解决不指定TValue,以便我可以将任意 lambda 传递给Foo(),并让它输出每个 lambda 引用的成员的路径?

如何将任意 lambda 作为参数传递给函数(以获取命名层次结构)

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;
    }

你必须声明一堆形式Foo<TSource, T1, ..., TN>的重载,类似于ActionFunc本身最多有 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);

但所有这些都是很多工作,收效甚微。

转换由节点类型 ExpressionType.ConvertUnaryExpression表示。在这种情况下,其 Operand 属性包含您要查找的MemberExpression

您需要一些可以使用任何表达式进行编码的代码,无需指定它是类型化表达式,因此只需使用基 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