如何获取表达式树中变量的值

How to get the value of a variable in an Expression Tree

我有一个问题困扰了我一段时间。如何检索在执行期间(完成之前)执行表达式树时创建的变量的 运行 时间值?当然,您可以根据 Lambda 变量中最后一个表达式的 return 类型获取最终值,但我感兴趣的是在执行过程中获取实际变量值。

下面我创建了一个简单的 For 循环示例,我在其中尝试输出以控制格式化字符串。对于此上下文,假设我不能简单地设置此子之外的某些引用 class 的 属性。我只是希望获得隐藏在 lambda 执行中的值。

    public static void WriteConsoleLineTemp(string Text, object obj1, object obj2)
    {
        Console.WriteLine(Text, obj1.ToString(), obj2.ToString());
    }

    private void TempSub()
    {
        LabelTarget label1 = Expression.Label();
        ParameterExpression IteratorInt = Expression.Variable(typeof(int), "i");
        ParameterExpression TempInteger = Expression.Variable(typeof(int), "int");
        ParameterExpression TempRandom = Expression.Variable(typeof(Random), "rand");
        MethodInfo ToStringMethod = typeof(object).GetMethod("ToString", Type.EmptyTypes);
        MethodInfo ConsoleWriteLine1 = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(object) });
        MethodInfo ConsoleWriteLine2 = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string), typeof(object[]) });
        MethodInfo ConsoleWriteLine3 = typeof(Form1).GetMethod("WriteConsoleLineTemp", new Type[] { typeof(string), typeof(int), typeof(int) });
        BlockExpression SuperTemp = Expression.Block(new[] { IteratorInt, TempInteger, TempRandom },
                Expression.Assign(TempRandom, Expression.Constant(new Random())),
                Expression.Loop(
                    Expression.Condition(Expression.GreaterThanOrEqual(IteratorInt, Expression.Constant(5)),
                        Expression.Return(label1),
                           Expression.Block(
                               Expression.AddAssign(IteratorInt, Expression.Constant(1)),
                               Expression.Assign(TempInteger, Expression.Call(TempRandom, typeof(Random).GetMethod("Next", new Type[] { typeof(int), typeof(int) }), Expression.Constant(0), Expression.Constant(10))),
                               //Expression.Call(null, ConsoleWriteLine1, Expression.Call(IteratorInt, ToStringMethod)), // This Works but only without format paramaters
                               //Expression.Call(null, ConsoleWriteLine1, Expression.Call(TempInteger, ToStringMethod)), //This Works but only without format paramaters
                               Expression.Call(null, ConsoleWriteLine2, Expression.Constant("Iteration {0}, Value = {1}"), Expression.Constant(new object[] { IteratorInt, TempInteger })),
                               Expression.Call(null, ConsoleWriteLine2, Expression.Constant("Iteration {0}, Value = {1}"), Expression.Constant(new object[] { Expression.Call(IteratorInt, ToStringMethod), Expression.Call(TempInteger, ToStringMethod) })),
                               Expression.Call(null, ConsoleWriteLine3, Expression.Constant("Iteration {0}, Value = {1}"), Expression.TypeAs(IteratorInt, typeof(object)), Expression.TypeAs(TempInteger, typeof(object))) // Works, but requires a specific sub

                               )
                        ),
                label1)
            );
        Action MyExecutor = (Action)Expression.Lambda(SuperTemp).Compile();
        MyExecutor();
    }

输出:

    Iteration i, Value = int
    Iteration i.ToString(), Value = int.ToString()
    Iteration 1, Value = 6
    Iteration i, Value = int
    Iteration i.ToString(), Value = int.ToString()
    Iteration 2, Value = 8
    Iteration i, Value = int
    Iteration i.ToString(), Value = int.ToString()
    Iteration 3, Value = 1
    Iteration i, Value = int
    Iteration i.ToString(), Value = int.ToString()
    Iteration 4, Value = 8
    Iteration i, Value = int
    Iteration i.ToString(), Value = int.ToString()
    Iteration 5, Value = 0

第三个 Call 表达式输出正确的结果,但需要一个特定的子(输出显示在每三行)。三个调用表达式中的第一个接近我想要的。最后,如果我可以使用类似的东西就好了:

    Expression.VariableValue(TempInteger)

其中输出是对象类型,然后可以自由地进行类型转换等,这样我就可以做到:

    Expression.Call(null, ConsoleWriteLine1, Expression.Constant("Iteration " + Expression.VariableValue(IteratorInt).ToString() + ", Value = " + Expression.VariableValue(TempInteger).ToString()));       

这只是一个简单的例子。其他相关问题包括输出具有特定类型异常的 Catch 块的结果,其中可以访问异常的值并适当地进行类型转换,而不是创建一个新的 sub 只是为了打印出异常信息。

有什么方法可以简单地检索 运行 时间值吗?

如果要获取变量的值,直接使用代表变量的ParameterExpression即可。如果你想使用 Console.WriteLine(string, object, object) 重载,那么唯一的问题是你需要从 int 转换为 object (C# 隐式执行):

MethodInfo ConsoleWriteLine3 = typeof(Console).GetMethod(
    "WriteLine", new Type[] { typeof(string), typeof(object), typeof(object) });

…

Expression.Call(
    null, ConsoleWriteLine3,
    Expression.Constant("Iteration {0}, Value = {1}"),
    Expression.Convert(IteratorInt, typeof(object)),
    Expression.Convert(TempInteger, typeof(object)))

如果您想改用 Console.WriteLine(string, object[]) 重载,您还需要创建 params 数组(C# 也为您做):

Expression.Call(
    null, ConsoleWriteLine2,
    Expression.Constant("Iteration {0}, Value = {1}"),
    Expression.NewArrayInit(
       typeof(object),
       Expression.Convert(IteratorInt, typeof(object)),
       Expression.Convert(TempInteger, typeof(object))))