NotJehov
NotJehov

Reputation: 43

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

I have a question that has been nagging me for some time. How do I retrieve the run-time value of variables created when executing an Expression Tree DURING the execution (prior to completion)? Of course, you could get the final value based on the last expression's return type in a Lambda variable, but I'm interested in obtaining the actual variable value in the midst of execution.

Below I've created a simple example of a For Loop, where I'm trying to output to console a formatted string. For this context, assume that I cannot simply set the property of some referenced class outside of this sub. I simply wish to obtain the values hidden within the lambda execution.

    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();
    }

which outputs:

    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

The third Call expression outputs the correct result, but requires a specific sub (output is shown on every third line). The first of the three Call Expressions is close to what I desire. Ultimately, it would be nice if I could use something like:

    Expression.VariableValue(TempInteger)

where the output is of type object, which can then be freely typecasted and such, so that I would be able to do:

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

This was only one simple example. Other relevant issues would include outputting the result of a Catch block with a specific type of exception, where the Exception's values can be accessed and typecasted appropriately, rather than creating a new sub just for printing out Exception info.

Is there any way to simply retrieve the run-time values?

Upvotes: 1

Views: 1898

Answers (1)

svick
svick

Reputation: 244787

If you want to get the value of the variable, just use the ParameterExpression that represents the variable. If you want to use the Console.WriteLine(string, object, object) overload, then the only problem is that you need to cast from int to object (which C# does implicitly):

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)))

If you wanted to use the Console.WriteLine(string, object[]) overload instead, you would also need to create the params array (which C# also does for you):

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

Upvotes: 1

Related Questions