簡體   English   中英

如何將任意lambda作為參數傳遞給函數(以獲取命名層次結構)?

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

我有一些(我認為是...)被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());
}

我的最終目標是Foo將輸出“名稱”,“ Id”和“地址”(此外,當傳遞諸如x => x.Foo.Bar.Baz類的lambda時,將輸出“ FooBarBaz”)。

但是,目前我沒有為Foo指定TValue 我不能,因為每個lambda都可以返回不同的值... 但是我不在乎,因為我需要的只是它們所引用的屬性路徑。

我嘗試使用object代替TValue ,但是當lambda返回一個int ,傳遞給GetHierarchicalName的lambda最終將是Convert而不是MemberExpression

如何避免不指定TValue ,以便可以將任意lambda傳遞給Foo() ,並讓其將路徑輸出到每個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);

但是,所有這些工作都是徒勞的。

轉換由具有NodeType ExpressionType.ConvertUnaryExpression ExpressionType.Convert 在這種情況下,其Operand屬性包含您要查找的MemberExpression

您需要一些可以與任何表達式一起編碼的代碼,無需指定它是類型化表達式,因此您可以僅使用基本Expression類。

這是我使用的實用程序類中的一些代碼,其中包含一些代碼來處理Converts並將其刪除。 請注意,我是在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; }
    }

這將產生:

Name    
Id    
Address

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM