Monday, August 4, 2014

Deep understanding expression. Part 1

Сегодня мы продолжим наше рассмотрение деревьев выражений в языке C#. В отличие от предыдущих двух тем по деревьям выражений, мы постараемся кратко охватить часть из 120 деревьев выражений на практике, а также рассмотрим их непосредственное отношение к C# коду. В этой статье будет много небольших примеров, чтобы можно было понять, как это работает, и использовать всю силу и мощь деревьев выражений в своих программах.

Add
Метод Expression.Add позвоялет создать выражение, содержащее бинарный оператор +, либо используя перегруженную версию с параметром MethodInfo для боле сложных сценариев.
Пример для простых значимых типов как int, long, short и т.д.:
Expression sumExpr = Expression.Add(
    Expression.Constant(2),
    Expression.Constant(3)
);
Console.WriteLine(sumExpr.ToString());
Console.WriteLine(Expression.Lambda<Func<int>>(sumExpr).Compile()());
Суммируем два константных значения 2 + 3.
Результат:
(2 + 3)
5
Пример конкатенации двух строк, перегруженная версия:
var method = typeof(string).GetMethod("Concat"new[] { typeof(string), typeof(string), });
Expression concatExpr = Expression.Add(
    Expression.Constant("Hello"),
    Expression.Constant(" World"),
    method
);
Console.WriteLine(concatExpr.ToString());
Console.WriteLine(Expression.Lambda<Func<string>>(concatExpr).Compile()());
Результат:
("Hello" + " World")
Hello World

AddAssign/AddAssignChecked
Метод Expression.AddAssign и Expression.AddAssignChecked очень похожи, с одним исключением, поэтому я решил их объединить. Эти методы позволяют сделать то же самое, что метод Add, и кроме этого, дают возможность присвоить полученное выражение указанной переменной. AddAssignChecked отличается только тем, что позволяет отслеживать переполнение OverflowException (ключевое слово checked). 
Поскольку реализация этих методов идентичная, приведу пример только для метода AddAssign.
Пример:
ParameterExpression sumExpr = Expression.Variable(typeof(int), "sum");

BlockExpression addAssignExpr = Expression.Block(
    new[] { sumExpr },
    Expression.Assign(sumExpr, Expression.Constant(2)),
    Expression.AddAssign(
        sumExpr,
        Expression.Constant(3)
    )
);

Console.WriteLine(Expression.Lambda<Func<int>>(addAssignExpr).Compile()());
Результат:
5
Есть аналогичный перегруженный вариант, который принимает параметр MethodInfo.

AddChecked
Это выражение идентично выражению Add, за исключением того, что происходит проверка на переполнение.
Пример:
Expression sumExpr = Expression.AddChecked(
    Expression.Constant(int.MaxValue),
    Expression.Constant(3)
);
Console.WriteLine(Expression.Lambda<Func<int>>(sumExpr).Compile()());
При запуске данного примера получим OverflowException.

And
Метод Expression.And представляет собой побитовую операцию "И". В языке C# это аналогично операции "&". Может работать как с булевыми значениями, так и с целыми числами.
Пример:
Expression andExpr = Expression.And(
    Expression.Constant(5),
    Expression.Constant(3)
);

Console.WriteLine(Expression.Lambda<Func<int>>(andExpr).Compile()());
1

Небольшое пояснение для тех, кому интересно, почему в данном случае получилась 1, и тех, кто слышал о битовых операциях лишь мельком. Дело в том, что в двоичном значении число 5 можно записать как 0101, а число 3 – как 0011. 
Побитовое "И" – это бинарная операция, действие которой эквивалентно применению логического "И" к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях операндов. Другими словами, если оба соответствующих бита операндов равны 1, результирующий двоичный разряд равен 1; если же хотя бы один бит из пары равен 0, результирующий двоичный разряд равен 0.
Выглядит это следующим образом:

AndAlso
Метод Expression.AndAlso представляет собой реализацию логического операнда AND. В языке C# аналог данной операции есть операция логического &&.
Вот как будет выглядеть таблица соответствия:
Пример:
Expression andExpr = Expression.AndAlso(
    Expression.Constant(true),
    Expression.Constant(false)
);

Console.WriteLine(Expression.Lambda<Func<bool>>(andExpr).Compile()());
Результат:
False

AndAssign
Метод Expression.AndAssign аналогичен побитовому методу And с единственным отличием: сохранение побитовой операции в переменной.
Пример:
var variableExpr = Expression.Variable(typeof(int), "a");

var assignExpr = Expression.Assign(variableExpr, Expression.Constant(5));

BlockExpression andAssignExpr = Expression.Block(
    new [] { variableExpr },
    assignExpr,
    Expression.AndAssign(
        variableExpr,
        Expression.Constant(3)
    )
);

Console.WriteLine(Expression.Lambda<Func<int>>(andAssignExpr).Compile()());
Результат:
1

ArrayAccess
Метод создает IndexExpression для доступа к массиву по индексу.
Пример с MSDN:
// This parameter expression represents a variable that will hold the array.
ParameterExpression arrayExpr = Expression.Parameter(typeof(int[]), "Array");

// This parameter expression represents an array index.           
ParameterExpression indexExpr = Expression.Parameter(typeof(int), "Index");

// This parameter represents the value that will be added to a corresponding array element.
ParameterExpression valueExpr = Expression.Parameter(typeof(int), "Value");

// This expression represents an array access operation.
// It can be used for assigning to, or reading from, an array element.
Expression arrayAccessExpr = Expression.ArrayAccess(
    arrayExpr,
    indexExpr
);

// This lambda expression assigns a value provided to it to a specified array element.
// The array, the index of the array element, and the value to be added to the element
// are parameters of the lambda expression.
Expression<Func<int[], int, int, int>> lambdaExpr = Expression.Lambda<Func<int[], int, int, int>>(
    Expression.Assign(arrayAccessExpr, Expression.Add(arrayAccessExpr, valueExpr)),
    arrayExpr,
    indexExpr,
    valueExpr
);

Console.WriteLine("Array Access Expression:");
Console.WriteLine(arrayAccessExpr.ToString());

Console.WriteLine("Lambda Expression:");
Console.WriteLine(lambdaExpr.ToString());

Console.WriteLine("The result of executing the lambda expression:");
Console.WriteLine(lambdaExpr.Compile().Invoke(new int[] { 10, 20, 30 }, 0, 5));
Результат:
Array Access Expression:
Array[Index]
Lambda Expression:
(Array, Index, Value) => (Array[Index] = (Array[Index] + Value))
The result of executing the lambda expression:
15

ArrayIndex
Expression.ArrayIndex возвращает BinaryExpression или MethodCallExpression, в зависимости от ранга массива (одномерный или многомерный массив). Возвращаемое выражение можно использовать для получения значения из массива.
Пример:
var arr = Expression.Parameter(typeof(int[]), "arr");
var body = Expression.ArrayIndex(arr, Expression.Constant(1));
var expr = Expression.Lambda<Func<int[], int>>(body, arr);

int[] vals = { 7, 8, 9 };
int i = expr.Compile().Invoke(vals);
Console.WriteLine(i);
Результат:
8

Пример с использованием многомерного массива MSDN:
string[,] gradeArray = { { "chemistry", "history", "mathematics" }, { "78", "61", "82" } };

Expression arrayExpression =
    Expression.Constant(gradeArray);

// Create a MethodCallExpression that represents indexing
// into the two-dimensional array 'gradeArray' at (0, 2).
// Executing the expression would return "mathematics".
MethodCallExpression methodCallExpression =
    Expression.ArrayIndex(
        arrayExpression,
        Expression.Constant(0),
        Expression.Constant(2));

Console.WriteLine(Expression.Lambda(methodCallExpression).Compile().DynamicInvoke());
Результат:
mathematics

ArrayLength
Создает UnaryExpression, представляющий выражение для получения длины одномерного массива.
Пример:
var p = Expression.Parameter(typeof(int[]), "arr");
var len = Expression.Lambda<Func<int[], int>>(
    Expression.ArrayLength(p), p).Compile();
Console.WriteLine("Lenght {0}", len.Invoke(new [] { 1,2,3,4,5,6,7,8,9}));
Результат:
Lenght 9

Assign
Создает BinaryExpression, представляющий выражение для операции присвоения.
Пример:
ParameterExpression sumExpr = Expression.Variable(typeof(int), "sum");
var assignexpr = Expression.Assign(sumExpr, Expression.Constant(5));

Expression blockExpr = Expression.Block(
    new [] { sumExpr },
    assignexpr
    );
Console.WriteLine(Expression.Lambda(blockExpr).Compile().DynamicInvoke());
Результат:
5
Bind
Создает MemberAssignment, представляющий инициализацию поля или свойства.
Пример:
class Program
{
    static void Main(string[] args)
    {
        var parameter = Expression.Parameter(typeof(int), "i");
        var newExpr = Expression.New(typeof(A));
        var bindExprs = new[]
        {
            Expression.Bind(typeof(A).GetProperty("B"), parameter)
        };

        var body = Expression.MemberInit(newExpr, bindExprs);
        var lambda = Expression.Lambda<Func<int, A>>(body, parameter);
        Console.WriteLine(lambda.Compile()(25));
        Console.ReadLine();
    }
}

public class A
{
    public int B { get; set; }
}
Результат:
CalculatorSample.A
Код аналогичен методу
i => new A { B = i }

Block
Создает BlockExpression, содержащий заданные выражения, и не содержит переменные. Мы уже использовали Expression.Block в примерах до этого несколько раз. Если не углубляться в детали, то метод Block позволяет просто создать выражение или группу выражений в виде логического блока кода.  Давайте для теста создадим простое выражение с использованием метода Block для вывода на экран числа 42.
Пример:
var bodyExpr = Expression.Block(
    Expression.Constant(42)
    );
Console.WriteLine(Expression.Lambda<Func<int>>(bodyExpr).Compile()());
Результат:
42
Break/Loop
Я решил объединить эти две операции, поскольку одна операция связана с циклом, вторая – с оператором break. Смысла особого их разделять нет, поскольку операция break чаще всего используется в цикле. Пример на языке C#:
for (int i = 0; i < 20; i++)
{
    if(i > 5)
        break;
}
На рисунке ниже вы можете увидеть данное сопоставление.
Приведем простой пример суммирования чисел, используя методы Expression.Loop и Expression.Break.
Пример:
// Creating a parameter expression.
ParameterExpression value = Expression.Parameter(typeof(int), "value");

// Creating an expression to hold a local variable. 
ParameterExpression sum = Expression.Parameter(typeof(int), "sum");

// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));

// Creating a method body.
BlockExpression block = Expression.Block(
    new[] { sum },
    Expression.Assign(sum, Expression.Constant(1)),
        Expression.Loop(
            Expression.IfThenElse(
                Expression.GreaterThan(value, Expression.Constant(1)),
                Expression.AddAssign(sum,
                    Expression.PostDecrementAssign(value)),
                Expression.Break(label, sum)
            ),
        label
    )
);

// Compile and run an expression tree.
int calculate = Expression.Lambda<Func<intint>>(block, value).Compile()(5);
Console.WriteLine(calculate);
Результат:
15
Если вы посмотрите на пример, который приведен в MSDN, то увидите, что он похож на приведенный здесь, так как он взят за основу. В том примере только было использовано высчитывание факториала, при этом это было сделано с ошибкой.
Пример выше можно интерпретировать в такой код:
public static int Sum(int value)
{
    int sum = 1;
    while (value > 1)
    {
        sum += value--;
    }
    return sum;
}

Call
Expression.Call создает выражение MethodCallExpression, которое позволяет вызывать методы, доступные в языке C#. Ниже в примере мы продемонстрируем вывод на консоль слов "Hello World" с помощью Console.WriteLine.
Пример:
var callExpression = Expression.Call(
                                        typeof(Console).GetMethod("WriteLine"new Type[] { typeof(String) }),
                                        Expression.Constant("Hello Wold"));
Console.WriteLine(Expression.Lambda(callExpression).Compile().DynamicInvoke());
Результат:
Hello Wold
Exceptions Expressions
Поскольку я решил рассмотреть сразу все деревья выражений, связанные с обработкой и генераций ошибок, постараюсь вынести их в детализированный подсписок.

TryCatch/Catch
Данные методы позволяют осуществить обработку ошибок с помощью деревьев выражений. Я решил не рассматривать отдельно обработку Expression.Catch, так как этот блок тесно связан с блоком Expression.TryCatch.
Пример:
TryExpression tryCatchExpr =
    Expression.TryCatch(
        Expression.Block(
            Expression.AddChecked(
                    Expression.Constant(int.MaxValue),
                    Expression.Constant(3)
                ),
            Expression.Constant("Try block")
        ),
        Expression.Catch(
            typeof(OverflowException),
            Expression.Constant("Catch block")
        )
    );
Console.WriteLine(Expression.Lambda<Func<string>>(tryCatchExpr).Compile()());
Результат:
Catch block
В примеры я старался использовать все то что мы использовали до этого кроме методов для построения деревьев TryCatch и Catch.

TryCatchFinally
Метод Expression.TryCatchFinally создает дерево выражений для обработки выражения try/catch/finally с множеством блоков catch и одним блоком finally.
Приведенное дерево выражений немного отличается построением от привычного нам в C# выражения try/catch/finally. При построении дерева с помощью метода TryCatchFinally сначала у нас параметром указывается тело выражения, затем блок expressioin, который будет вызван на finally, и последним идет массив блоков catch для обработки. Это немного нарушает логику понимания, но это сделано по той причине, что массив блоков catch передается с указанием ключевого слова params.
По сути, все эти методы интерпретируются и используют базовый метод MakeTry, который приведен выше. При желании вы можете использовать этот метод напрямую.
Пример:
TryExpression tryCatchFinallyExpr =
    Expression.TryCatchFinally(
        Expression.Block(
            Expression.AddChecked(
                    Expression.Constant(int.MaxValue),
                    Expression.Constant(3)
                ),
            Expression.Constant("Try block")
        ),
        Expression.Call(typeof(Console).GetMethod("WriteLine"new Type[] { typeof(string) }), Expression.Constant("Finally block")),
        Expression.Catch(
            typeof(OverflowException),
            Expression.Constant("Catch block")
        )
    );
Console.WriteLine(Expression.Lambda<Func<string>>(tryCatchFinallyExpr).Compile()());
Результат:
Finally block
Catch block

TryFault
Метод Expression.TryFault создает дерево с блоком try и блоком fault, не используя блоки catch.
С примером здесь туговато для языка C#, потому как ключевое слова fault генерирует компилятор.
private bool MoveNext()
{
    bool flag;
    try
    {
        // [...]
    }
    fault
    {
        this.Dispose();
    }
    return flag;
}
На C# этот код еквивалентен коду, приведенному ниже.
private bool MoveNext()
{
    bool success = false;
    try
    {
        ... stuff ...
        success = true
    }
    finally
    {
        if (!success)
        {
            Dispose();
        }
    }
}
Поэтому код для TryFault будет по логике похож на TryFinally, который приведу ниже в примере.

TryFinally
Метод Expression.TryFinally создает дерево, которое представляет собой блок try и блок finally без использования блоков catch.
Пример:
TryExpression tryFinallyExpr =
Expression.TryFinally(
    Expression.Block(
        Expression.Add(
                Expression.Constant(3),
                Expression.Constant(3)
            ),
        Expression.Constant("Try block")
    ),
    Expression.Call(typeof(Console).GetMethod("WriteLine"new Type[] { typeof(string) }), Expression.Constant("Finally block"))
);
Console.WriteLine(Expression.Lambda<Func<string>>(tryFinallyExpr).Compile()());
В примере я специально в блоке try не генерировал ошибку.
Результат:
Finally block
Try block

Throw
Метод Expression.Throw создает дерево выражений, которое позволяет пробрасывать исключение.
Пример с пробросом exception – DevideByZeroException.
TryExpression tryCatchExpr =
    Expression.TryCatch(
        Expression.Block(
            Expression.Throw(Expression.Constant(new DivideByZeroException())),
            Expression.Constant("Try block")
        ),
        Expression.Catch(
            typeof(DivideByZeroException),
            Expression.Constant("Catch block")
        )
    );

Console.WriteLine(Expression.Lambda<Func<string>>(tryCatchExpr).Compile()());
Результат:
Catch block
Rethrow
Метод Expression.Rethrow создает дерево UnaryExpression и позволяет повторно генерировать исключения.
Пример я языке C# это может выглядеть следующим образом:
try
{
    throw new DivideByZeroException();
}
catch (DivideByZeroException)
{
    throw new ArgumentException("Rethow exception");
}
Пример с использованием expression:
TryExpression tryCatchExpr =
    Expression.TryCatch(
        Expression.Block(
            Expression.Throw(Expression.Constant(new DivideByZeroException())),
            Expression.Constant("Try block")
        ),
        Expression.Catch(
            typeof(DivideByZeroException),
            Expression.Block(
                Expression.Rethrow(),
                Expression.Constant("Rethow block")
            )
        )
    );

Console.WriteLine(Expression.Lambda<Func<string>>(tryCatchExpr).Compile()());
Ниже приведенный код аналогичен такому C# коду:
try
{

}
catch (DivideByZeroException)
{
    throw;
}

MakeTry
Метод Expression.MakeTry создает TryExpression, который представляет собой блок try с указанными элементами. По сути, все методы с приставкой Try, которые мы рассматривали выше, используют за базовый метод MakeTry.
public static TryExpression TryFault(Expression body, Expression fault)
{
    return MakeTry(null, body, null, fault, null);
}

public static TryExpression TryFinally(Expression body, Expression @finally)
{
    return MakeTry(null, body, @finally, nullnull);
}

public static TryExpression TryCatch(Expression body, params CatchBlock[] handlers)
{
    return MakeTry(null, body, nullnull, handlers);
}

public static TryExpression TryCatchFinally(Expression body, Expression @finally, params CatchBlock[] handlers)
{
    return MakeTry(null, body, @finally, null, handlers);
}
Вы можете сами в этом убедится посмотрев исходники которые доступный в онлайн режиме по ссылке #TryExpression.cs.
Пример:
TryExpression tryCatchExpr = Expression.MakeTry(
    null,                              //The result type of the try expression. If null, bodh and all handlers must have identical type.
    Expression.Block(                  //The body of the try block.
        Expression.AddChecked(
            Expression.Constant(int.MaxValue),
            Expression.Constant(3)
            ),
        Expression.Constant("Try block")
        ),
    null,                             //The body of the finally block. Pass null if the try block has no finally block associated with it.
    null,                             //The body of the fault block. Pass null if the try block has no fault block associated with it. 
    new[] {                          //A collection of CatchBlock's representing the catch statements to be associated with the try block.
    Expression.Catch(
        typeof (OverflowException),
        Expression.Constant("Catch block")
        )}
    );

Console.WriteLine(Expression.Lambda<Func<string>>(tryCatchExpr).Compile()());
Результат:
Catch block
MakeCatch
Метод Expression.MakeCatchBlock представляет собой catch с указанными элементами. Этот метод базовый для методов Expression.Catch.
Пример:
TryExpression tryCatchExpr = 
    Expression.TryCatch(
        Expression.Block(
            Expression.Throw(Expression.Constant(new DivideByZeroException())),
            Expression.Constant("Try block")
        ),
        Expression.MakeCatchBlock(
            typeof(DivideByZeroException),
            null,
            Expression.Constant("Catch block"),
            null
        )
    );

Console.WriteLine(Expression.Lambda<Func<string>>(tryCatchExpr).Compile()());
Результат:
Catch block
На этой ноте мы закончим разбирать работу с построением деревьев выражений для работы с обработкой и генерацией ошибок.
Coalesce
Метод Expression.Coalesce создает BinaryExpression, представляющий оператор объединения со значение NULL (оператор ?? ).
Пример:
static void Main(string[] args)
{
    Expression coalesceExpr = Expression.Coalesce(
        Expression.Constant(null), Expression.Constant(6));

    Console.WriteLine(Expression.Lambda<Func<object>>(coalesceExpr).Compile()());
    Console.ReadLine();
}
Результат:
6

Condition
Создает объект ConditionalExpression, представляющий условный оператор. Это базовый метод для методов IfThen и IfThenElse которые мы рассмотрим в следующей статье.
Пример:
static void Main(string[] args)
{
    Expression conditionExpr = Expression.Condition(
                    Expression.Constant(100 > 10),
                    Expression.Constant("number is greater than 10"),
                    Expression.Constant("number is smaller than 10")
                    );
    
    Console.WriteLine(
        Expression.Lambda<Func<string>>(conditionExpr).Compile()());
    Console.ReadLine();
}
Результат:
number is greater than 10

Constant
Expression.Constant создает выражение, содержащее в себе постоянное значение.
Пример:
static void Main(string[] args)
{
    Expression constantExpr = Expression.Constant(5.5);
    Console.WriteLine(constantExpr);
    Console.ReadLine();
}
Результат:
5.5

Continue
Expression.Continue создает объект GotoExpression, представляющий оператор continue.
Пример:
static void Main(string[] args)
    {
        LabelTarget breakLabel = Expression.Label();

        // A label that is used by the Continue statement and the loop it refers to.
        LabelTarget continueLabel = Expression.Label();

        // This expression represents a Continue statement.
        Expression continueExpr = Expression.Continue(continueLabel);

        // A variable that triggers the exit from the loop.
        ParameterExpression count = Expression.Parameter(typeof(int));

        // A loop statement.
        Expression loopExpr = Expression.Loop(
            Expression.Block(
                Expression.IfThen(
                    Expression.GreaterThan(count, Expression.Constant(3)),
                    Expression.Break(breakLabel)
                ),
                Expression.PreIncrementAssign(count),
                Expression.Call(
                            null,
                            typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) }),
                            Expression.Constant("Loop")
                        ),
                continueExpr,
                Expression.PreDecrementAssign(count)
            ),
            breakLabel,
            continueLabel
        );

        Expression.Lambda<Action<int>>(loopExpr, count).Compile()(1);
        Console.ReadLine();
    }
}
Результат:
Loop
Loop
Loop

Convert/ConvertChecked
Создает выражение UnaryExpression, представляющее операцию преобразования типа. Отличие метода Convert от ConvertChecked заключается в том, что метод ConvertChecked может генерировать ошибку OverflowException в случае переполнения.

Пример:
static void Main(string[] args)
{
    Expression convertExpr = Expression.Convert(
                                Expression.Constant(5.5),
                                typeof(Int16)
                            );

    // Print out the expression.
    Console.WriteLine(convertExpr.ToString());

    // The following statement first creates an expression tree,
    // then compiles it, and then executes it.
    Console.WriteLine(Expression.Lambda<Func<Int16>>(convertExpr).Compile()());

    Console.ReadLine();
}
Результат:
Convert(5.5)
5
DebugInfo/ClearDebugInfo
Методы DebugInfo и ClearDebugInfo тесно связаны между собой для вызова или очистки точки отладки. Это позволяет настраивать отладчик для выбора правильного исходного кода при отладке. Это одни из самых сложных функций из деревьев выражений с которыми мне пришлось столкнутся. Пример для построения данного дерева взят с данного источника Using DebugInfo.
Пример:
public class Program
{
    static void Main(string[] args)
    {
        Expression.Lambda(Dbg())
            .Compile(DebugInfoGenerator.CreatePdbGenerator())
            .DynamicInvoke();
            
        Console.ReadLine();
    }

    public static void P()
    {
        var trace = new System.Diagnostics.StackTrace(true);

        foreach (var frame in trace.GetFrames())
        {
            Console.WriteLine(frame.ToString());
        }
    }

    static Expression Dbg()
    {
        return Expression.Block(
            Expression.DebugInfo(
                Expression.SymbolDocument("aaa.cs"),
                1, 1,
                2, 2),
            Expression.Call(
                typeof(Program).GetMethod("P")),
            Expression.Throw(
                Expression.New(
                    typeof(System.Exception).GetConstructor(
                        new Type[] { typeof(string) }),
                    Expression.Constant("Test"))),
            Expression.ClearDebugInfo(
                Expression.SymbolDocument("aaa.cs"))
            );
    }
}
Результат:
P at offset 92 in file:line:column d:\Projects\TestProjects\UnderExpressionSampl
e\UnderExpressionSample\Program.cs:24:13

lambda_method at offset 43 in file:line:column <filename unknown>:0:0

InvokeMethod at offset 0 in file:line:column <filename unknown>:0:0

UnsafeInvokeInternal at offset 229 in file:line:column <filename unknown>:0:0

DynamicInvokeImpl at offset 106 in file:line:column <filename unknown>:0:0

Main at offset 146 in file:line:column d:\Projects\TestProjects\UnderExpressionS
ample\UnderExpressionSample\Program.cs:15:13

Decrement/Increment
Функция Expression.Decrement создает UnaryExpression представляющий операцию уменьшения на единицу выражения. Expression.Increment, наоборот, увеличивает значение выражения на 1.
Пример:
Expression decrementExpr = Expression.Decrement(Expression.Constant(5));
Console.WriteLine(Expression.Lambda<Func<int>>(decrementExpr).Compile()());
Expression incrementExpr = Expression.Increment(Expression.Constant(5));
Console.WriteLine(Expression.Lambda<Func<int>>(incrementExpr).Compile()());
Результат:
4
6
Default
Метод Expression.Default создает DefaultExpression и позволяет установить значение по умолчанию для конкретного типа
Пример:
Expression defaultExpression = Expression.Default(typeof (int));
Console.WriteLine(Expression.Lambda<Func<int>>(defaultExpression).Compile()());
Результат:
0
Divide/DivideAssign
Метод Expression.Divide создает бинарное дерево выражения для представления математической операции деления. В отличие от метода Divide, этот метод позволяет сохранить результат деления в переменную.
Пример:
Expression divideExpression =
    Expression.Divide(
        Expression.Constant(4),
        Expression.Constant(2)
    );
Console.WriteLine(Expression.Lambda<Func<int>>(divideExpression).Compile()());

ParameterExpression parameterExpr = Expression.Parameter(typeof (int), "n");
BlockExpression divideAssignexpr = Expression.Block(
    new ParameterExpression[] { parameterExpr },
    Expression.Assign(parameterExpr, Expression.Constant(10)),
    Expression.DivideAssign(
        parameterExpr,
        Expression.Constant(2)
    )
);

Console.WriteLine(Expression.Lambda<Func<int>>(divideAssignexpr).Compile()());
Результат:
2
5
Dynamic
Метод Expression.Dynamic создает выражение DynamicExpression, которое представляет динамическую операцию, привязанную с использованием указанного объекта CallSiteBinder.
Пример:
var x = Expression.Parameter(typeof(object), "x");
var y = Expression.Parameter(typeof(object), "y");
var binder = Binder.BinaryOperation(
    CSharpBinderFlags.None, ExpressionType.Add, typeof(Program),
    new CSharpArgumentInfo[] {
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)});
Func<dynamic, dynamic, dynamic> f =
    Expression.Lambda<Func<object, object, object>>(
        Expression.Dynamic(binder, typeof(object), x, y),
        new[] { x, y }
    ).Compile();

Console.WriteLine(f("Hello", " World!!!"));
Результат:
Hello World!!!
ElementInit
Метод ElementInit представляет собой инициализатор для одиночного элемента коллекции. Например, List.Add(), Dictionary.Add и другие.
Пример:
var methodInfo = typeof (List<string>).GetMethod("Add");
var elementInitExpr = Expression.ElementInit(
    methodInfo,
    new[]
    {
        Expression.Constant("Hello")
    });

Console.WriteLine(elementInitExpr);
Результат:
Void Add(System.String)("Hello")
Интересная особенность данного метода заключается в том, что у вас не обязательно должна быть коллекция. Главное, чтобы в вашем классе был метод Add.
class Program
{
    static void Main(string[] args)
    {
        var methodInfo = typeof(Program).GetMethod("Add");
        var elementInitExpr = Expression.ElementInit(
            methodInfo,
            Expression.Constant(2));

        Console.WriteLine(elementInitExpr);

        Console.ReadLine();
    }

    public void Add(int a)
    {
        Console.WriteLine(a);
    }
}
Это работает по той простой причине, что классе ElementInit зашита проверка на метод Add. Вот как выглядит эта проверка:
private static void ValidateElementInitAddMethodInfo(MethodInfo addMethod)
{
    ValidateMethodInfo(addMethod);
    ParameterInfo[] pis = addMethod.GetParametersCached();
    if (pis.Length == 0)
    {
        throw Error.ElementInitializerMethodWithZeroArgs();
    }
    if (!addMethod.Name.Equals("Add", StringComparison.OrdinalIgnoreCase))
    {
        throw Error.ElementInitializerMethodNotAdd();
    }
    if (addMethod.IsStatic)
    {
        throw Error.ElementInitializerMethodStatic();
    }
    foreach (ParameterInfo pi in pis)
    {
        if (pi.ParameterType.IsByRef)
        {
            throw Error.ElementInitializerMethodNoRefOutParam(pi.Name, addMethod.Name);
        }
    }
}
Empty
Метод Expression.Empty создает пустое выражение типа void. Это выражение можно использовать, когда какой-то метод ожидает выражение, которое не требует каких-либо действий.
Пример:
DefaultExpression emptyExpr = Expression.Empty();

var emptyBlock = Expression.Block(emptyExpr);

Console.WriteLine(emptyExpr);
Результат:
default(Void)
Equal
Expression.Equal создает BinaryExpression, которое представляет собой операцию сравнения на равенство.
Пример:
ParameterExpression a = Expression.Parameter(typeof(int), "a");
ParameterExpression b = Expression.Parameter(typeof(int), "b");
Expression assignA = Expression.Assign(a, Expression.Constant(12));
Expression assignB = Expression.Assign(b, Expression.Constant(13));

Expression blockAExpr = Expression.Block(
    new ParameterExpression[] { a },
    assignA
    );
Expression blockBExpr = Expression.Block(
    new ParameterExpression[] { b },
    assignB
    );
Expression equalExpression = Expression.Equal(blockAExpr, blockBExpr);

Console.WriteLine(Expression.Lambda<Func<bool>>(equalExpression).Compile()());
Результат:
False
Или более простой пример:
Expression equalExpression = Expression.Equal(
    Expression.Constant(12),
    Expression.Constant(13));

Console.WriteLine(Expression.Lambda<Func<bool>>(equalExpression).Compile()());
На этой ноте мы закончим данную статью и продолжим рассмотрение деревьев выражений в следующей части данной статьи, поскольку переработать сразу такое огромное количество информации очень сложно. После того как мы детально рассмотрим данную тему о деревьях выражений, мы постараемся рассмотреть более весомый пример. 

No comments:

Post a Comment