Page 47 - MSDN Magazine, September 2019
P. 47
In C#, the code should look like this:
// Using static System.Linq.Expressions.Expression
ParameterExpression prm = Parameter(typeof(int), "i"); Expression expr = Call(
prm,
typeof(int).GetMethods("ToString", new [] {}) );
While building expression trees in this manner usually requires a fair amount of reflection and multiple calls to the factory meth- ods, you have greater flexibility to tailor the precise expression tree needed. With the compiler syntax, you would have to write out all possible variations of the expression tree that your program might ever require.
Closed-over Variables
In addition, the factory method API allows you to build some kinds of expressions that aren’t currently supported in compiler- generated expressions. A few examples:
Statements such as a System.Void-returning Conditional- Expression, which corresponds to If..Then and If..Then..Else..End If (in C# known as if (...) { ...} else { ... }); or a TryCatchExpression representing a Try..Catch or a try { ... } catch (...) { ... } block.
Assignments Dim x As Integer: x = 17.
Blocks for grouping multiple statements together.
For example, consider the following Visual Basic code:
Dim msg As String = "Hello!"
If DateTime.Now.Hour > 18 Then msg = "Good night" Console.WriteLine(msg)
or the following equivalent C# code:
string msg = "Hello";
if (DateTime.Now.Hour > 18) {
msg = "Good night"; }
Console.WriteLine(msg);
You could construct a corresponding expression tree in Visual Basic or in C# using the factory methods, as shown in Figure 5.
Figure 5 Blocks, Assignments and Statements in Expression Trees
When a lambda expression references a variable defined outside of it, we say the lambda expression “closes over” the variable. The compiler has to give this variable some special attention, because the lambda expression might be used after the value of the variable has been changed, and the lambda expression is expected to reference the new value. Or vice versa, the lambda expression might change the value, and that change should be visible outside of the lambda expression. For example, in the following C# code:
var i = 5;
Action lmbd = () => Console.WriteLine(i); i = 6;
lmbd();
or in the following Visual Basic code:
Dim i = 5
Dim lmbd = Sub() Console.WriteLine(i) i= 6
lmbd()
the expected output would be 6, not 5, because the lambda ex- pression should use the value of i at the point when the lambda expression is invoked, after i has been set to 6.
The C# compiler accomplishes this by creating a hidden class with the needed variables as fields of the class, and the lambda expressions as methods on the class. The compiler then replaces all references to that variable with member access on the class instance. The class instance doesn’t change, but the value of its fields can. The resulting IL looks something like the following in C#:
[CompilerGenerated]
private sealed class <>c__DisplayClass0_0 {
public int i;
internal void <Main>b__0() => Console.WriteLine(i); }
var @object = new <>c__DisplayClass0_0(); @object.i = 5;
Action lmbd = @object.<Main>b__0; @object.i = 6;
lmbd();
The Visual Basic compiler does something similar, with one dif- ference: $VB$Local is prepended to the property name, like so:
<CompilerGenerated> Friend NotInheritable Class _Closure$__0-0 Public $VB$Local_i As Integer
Sub _Lambda$__0()
Console.WriteLine($VB$Local_i) End Sub
End Class
Dim targetObject = New _Closure$__0-0 targetObject With { .$VB$Local_i = 5 } Dim lmbd = AddressOf targetObject. _Lambda$__0
targetObject.i = 6
lmbd()
‘ Visual Basic
' Imports System.Linq.Expressions.Expression
Dim msg = Parameter(GetType(String), "msg") Dim body = Block(
Assign(msg, Constant("Hello")), IfThen(
GreaterThan( MakeMemberAccess(
MakeMemberAccess(
Nothing, GetType(DateTime).GetMember("Now").Single
),
GetType(DateTime).GetMember("Hour").Single ),
Constant(18) ),
Assign(msg, Constant("Good night")) ),
[Call](
GetType(Console).GetMethod("WriteLine", { GetType(string) }), msg
) )
// C#
// Using static System.Linq.Expressions.Expression
var msg = Parameter(typeof(string), "msg"); var expr = Lambda(
Block(
Assign(msg, Constant("Hello")), IfThen(
GreaterThan( MakeMemberAccess(
MakeMemberAccess( null,
typeof(DateTime).GetMember("Now").Single() ),
typeof(DateTime).GetMember("Hour").Single() ),
Constant(18) ),
Assign(msg, Constant("Good night")) ),
Call(
typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), msg
) )
);
msdnmagazine.com
September 2019 39