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

时间:2015-03-31 03:38:56

标签: c# lambda linq-expressions

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

下面我创建了一个For循环的简单示例,我尝试输出以控制格式化字符串。对于此上下文,假设我不能简单地在此sub之外设置某些引用类的属性。我只想获得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表达式输出正确的结果,但需要一个特定的子句(输出显示在每三行)。三个Call表达式中的第一个接近我想要的。最终,如果我可以使用类似的东西,那就太好了。

    Expression.VariableValue(TempInteger)

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

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

这只是一个简单的例子。其他相关问题包括输出具有特定类型异常的Catch块的结果,其中可以正确访问和类型化异常值,而不是仅为打印出异常信息创建新子。

有没有办法简单地检索运行时值?

1 个答案:

答案 0 :(得分:1)

如果要获取变量的值,只需使用代表变量的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))))