Как получить значение переменной в дереве выражений

1

У меня есть вопрос, который время от времени натыкался на меня. Как получить значение времени выполнения переменных, созданных при выполнении дерева выражений ВО ВРЕМЯ выполнения (до завершения)? Конечно, вы можете получить окончательное значение на основе последнего типа возвращаемого выражения в переменной Lambda, но я заинтересован в получении фактического значения переменной в середине выполнения.

Ниже я создал простой пример For Loop, где я пытаюсь вывести на консоль форматированную строку. Для этого контекста предположим, что я не могу просто установить свойство некоторого ссылочного класса вне этого под. Я просто хочу получить значения, скрытые в выполнении лямбды.

    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 выводит правильный результат, но требует определенного sub (вывод показан на каждой третьей строке). Первый из трех выражений звонка близок к тому, что я хочу. В конечном счете, было бы неплохо, если бы я мог использовать что-то вроде:

    Expression.VariableValue(TempInteger)

где выход представляет собой объект типа, который затем можно свободно вводить и таким образом, чтобы я мог:

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

Это был только один простой пример. Другие релевантные проблемы включают вывод результата блока Catch с особым типом исключения, при котором значения исключений могут быть доступны и соответствующим образом отображены, вместо создания нового субфайла только для распечатки информации об исключении.

Есть ли способ просто получить значения времени выполнения?

  • 1
    Я не уверен, что ты имеешь в виду. После того, как вы скомпилируете выражение, у вас есть обычный метод. Исходные объекты выражения не имеют отношения к этому методу; если бы они были, то что бы значило исследовать один из этих объектов после того, как вы скомпилировали выражение два или более раз? К какому методу они будут относиться? Итак, учитывая, что скомпилированное выражение является обычным методом, ваш вопрос, кажется, сводится к тому, «как мне узнать, какое значение имеет локальная переменная в методе во время выполнения метода». И ответ на этот вопрос - «нет, если метод не передает его вам».
Теги:
lambda
linq-expressions

1 ответ

1
Лучший ответ

Если вы хотите получить значение переменной, просто используйте ParameterExpression, представляющий переменную. Если вы хотите использовать перегрузку Console.WriteLine(string, object, object), тогда единственная проблема заключается в том, что вам нужно отбрасывать из int в object (что С# делает неявным):

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 (который С# также делает для вас):

Expression.Call(
    null, ConsoleWriteLine2,
    Expression.Constant("Iteration {0}, Value = {1}"),
    Expression.NewArrayInit(
       typeof(object),
       Expression.Convert(IteratorInt, typeof(object)),
       Expression.Convert(TempInteger, typeof(object))))
  • 0
    Вздох, конечно, это была простая вещь, которая была забыта; NewArrayInit фактически пропускает переменные для их преобразования, в отличие от объявления нового объекта [], который просто передает сами объекты ParameterExpression, что бесполезно. Спасибо, это беспокоило меня уже некоторое время.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню