简体   繁体   English

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

[英]How to pass arbitrary lambdas as params to function (to get naming hierarchy)?

I have some (what I think are...) MemberExpression 's wrapped in lambas. 我有一些(我认为是...)被MemberExpression包装的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());
}

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). 我的最终目标是Foo将输出“名称”,“ Id”和“地址”(此外,当传递诸如x => x.Foo.Bar.Baz类的lambda时,将输出“ FooBarBaz”)。

However, at the moment I'm not specifying TValue for Foo ; 但是,目前我没有为Foo指定TValue 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. 我不能,因为每个lambda都可以返回不同的值... 但是我不在乎,因为我需要的只是它们所引用的属性路径。

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 . 我尝试使用object代替TValue ,但是当lambda返回一个int ,传递给GetHierarchicalName的lambda最终将是Convert而不是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? 如何避免不指定TValue ,以便可以将任意lambda传递给Foo() ,并让其将路径输出到每个lambda所引用的成员?

int values need to be boxed to be represented as objects, that's why you're getting a Convert expression. 需要将int值装箱以表示为对象,这就是为什么要获取Convert表达式的原因。 You have to get the Convert expression's Operand instead of Body : 您必须获取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;
    }

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. 您必须声明一堆形式为Foo<TSource, T1, ..., TN>重载,类似于ActionFunc自身如何对多达16个参数重载。 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的额外参数:

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 . 转换由具有NodeType ExpressionType.ConvertUnaryExpression ExpressionType.Convert In this case, its Operand property contains the MemberExpression you're looking for. 在这种情况下,其Operand属性包含您要查找的MemberExpression

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. 您需要一些可以与任何表达式一起编码的代码,无需指定它是类型化表达式,因此您可以仅使用基本Expression类。

Here is some code from the utilities class I use, it has some code to handle the Converts and strip them out. 这是我使用的实用程序类中的一些代码,其中包含一些代码来处理Converts并将其删除。 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. 请注意,我是在LinqPad中编写的,因此如果要在其他位置运行Dump(),则可能需要使用Console.WriteLine之类的东西来替换它。

    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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM