Сегодня мы продолжим наше рассмотрение деревьев
выражений в языке 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
// 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));
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<int, int>>(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.AddChecked(
Expression.Constant(int.MaxValue),
Expression.Constant(3)
),
Expression.Constant("Try block")
),
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, который приведен выше. При желании вы можете
использовать этот метод напрямую.
Пример:
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")),
typeof(OverflowException),
Expression.Constant("Catch block")
)
);
Console.WriteLine(Expression.Lambda<Func<string>>(tryCatchFinallyExpr).Compile()());
Результат:
Finally block
Catch block
TryFault
С примером здесь туговато для языка 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
Пример с пробросом 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, null, null);
}
public static TryExpression TryCatch(Expression body, params CatchBlock[] handlers)
{
return MakeTry(null, body, null, null, 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
static void Main(string[] args)
{
Expression
constantExpr = Expression.Constant(5.5);
Console.WriteLine(constantExpr);
Console.ReadLine();
}
Результат:
5.5
Пример:
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
Пример:
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