如何在 C# 中捕获表达式树

How to capture an expression tree in C#

我正在编写一些 debug/test 代码,我希望在其中显示原始表达式并对任意表达式求值。

对于(简单的)示例:

IList<string> myString = /* Some string collection expression */

ShowMe(myString.Select(s => s.ToLower()));

我在哪里实施 ShowMe 类似的东西:

public void ShowMe(/* Not sure what has to go here */)
{
    /* Not sure what has to go here */
    Console.WriteLine(expression.ToString();
    IEnumerable result = expression.Evaluate(); // or something
    foreach(item in result)
    {
        Console.WriteLine(/* etc. */)
    }
}

结果将写入控制台:

myString.Select(s => s.ToLower())

(first item)

(next item

(etc...)

换句话说,我的ShowMe方法对表达式树而不是表达式的值进行操作,因此它既可以显示给定的表达式,也可以显示计算结果。

我不能简单地将 ShowMe 声明为:

public void ShowMe(Expression expr)

...但是如果我声明为

public void ShowMe(Expression<Func<Enumerable>> expr)

... 它 排序 有效 - 我必须这样用 lambda 表达式调用我的方法:

ShowMe(() => myString.Select(s => s.ToLower()))

...我不想这样做。

我有理由相信这是可以做到的……FluentAssertions 做到了。例如:如果我执行下面一行测试代码:

(1 + 1).Should.Be(3)

我得到以下结果:

Expected (1 + 1) to be 3, but found 2.

FluentAssertion 已计算表达式 (1+1) 并捕获表达式树,因此能够显示它计算的原始表达式。

我看不出这是怎么做到的,但我想做类似的事情。我该怎么做?

这本身是任何方法都做不到的。

所有这些库只解析堆栈跟踪并提取文件名和行号。然后从给定行的源代码文件中提取表达式(同时这包括一些 parsing/validation)。

同样值得注意的是,如果源代码不可用,表达式将无法显示exists/is。

找出可接受的折衷方案:

public static class ObjectHelper
{
    public static void ToConsole<T>(this IEnumerable<T> enumerable, Expression<Func<T,object>> expr)
        where T:class
    {
        var fn = expr.Compile();

        var result = enumerable.Select(s => fn(s));

        Console.WriteLine($"My data selected as {PrettyPrintExpression(expr)}");
        foreach(var element in result)
        {
            Console.WriteLine(/*  etc.  */);
        }
    }

    private static string PrettyPrintExpression(Expression<Func<T,object>> expr)
    {
        // Walk the expression tree to print as desired
    }
}

...我可以将其调用为:

IList<MyObject> list = /* etc. */
list.ToConsole(s => new{/* any members I want out of MyObject */});