Skip to content

BlockExpression rendering in C# and Visual Basic renderers

Zev Spitz edited this page May 9, 2021 · 4 revisions

BlockExpression allows putting multiple expressions where previously only a single expression would have been allowed. This allows constructing expression trees with multiple statements, like the following:

if (DateTime.Now.Hour < 18) {
    Console.WriteLine("Good day");
    Console.WriteLine("Have a nice day");
} else {
    Console.WriteLine("Good night");
    Console.WriteLine("Have a nice night");
}

via factory methods:

var writeline = typeof(Console).GetMethods("WriteLine", new [] { typeof(string) });
Expression expr = IfThenElse(
    LessThanOrEqual(
        Property(
            Property(null, typeof(DateTime).GetProperty("Now")),
            "Hour"
        ),
        Constant(18)
    ),
    // the following two Block calls each produce a BlockExpression
    Block(
        Call(writeline, Constant("Good day!")),
        Call(writeline, Constant("Have a nice day!"))
    ),
    Block(
        Call(writeline, Constant("Good night!")),
        Call(writeline, Constant("Have a nice night!"))
    )
);

Since BlockExpression is an expression like any other, it could theoretically be put in places where C# And VB.NET don't allow it, such as in the test of an if...else block:

if ({
    true;
    true;
}) {
    Console.WriteLine(true);
}

The following sections describe how BlockExpression is rendered in the C# and Visual Basic renderers.

Rendering BlockExpressions in the C# renderer

When an inline expression is expected (such as in a ternary if, or some binary operation), a BlockExpression is rendered using parentheses and a C-style comma operator:

() => (
    true,
    true
) ? true : false

which has precisely equivalent semantics to the BlockExpression -- multiple sub-expressions are supported, but the value of the entire expression is the value of the last sub-expression.

The comma operator is also used for BlockExpressions in a test clause, such as the test of an if (...) {...} block:

if (
    true,
    true
) {
    Console.WriteLine(true);
}

BlockExpressions which appear when a given syntax expects a block is expected are rendered with curly braces and semicolons (when needed):

if (true) {
    Console.WriteLine(true);
    Console.WriteLine(true);
}

A BlockExpression whose parent is another BlockExpression will not be rendered as a separate block:

// using static System.Linq.Expressions.Expression;
// using ExpressionToString;

var writeline = typeof(Console).GetMethods("WriteLine", new Type[] { });
Expression expr = Block(
    Call(writeline),
    Block(
        Call(writeline),
        Call(writeline)
    ),
    Call(writeline)
);
Console.WriteLine(expr.ToString("C#"));
/*
    (
        Console.WriteLine(),
        Console.WriteLine(),
        Console.WriteLine(),
        Console.WriteLine()
    )
*/

unless the inner BlockExpression introduces variables into the local scope:

// using static System.Linq.Expressions.Expression;
// using ExpressionToString;

var writeline = typeof(Console).GetMethods("WriteLine", new Type[] { });
Expression expr = Block(
    Call(writeline),
    Block(
        new[] { Parameter(typeof(string), "s1") },
        Call(writeline),
        Call(writeline)
    ),
    Call(writeline)
);
Console.WriteLine(expr.ToString("C#"));
/*
    (
        Console.WriteLine(),
        (
            string s1,
            Console.WriteLine(),
            Console.WriteLine()
        ),
        Console.WriteLine()
    )
*/

Note that within the comma operator, statements such as variable declaration (if (string s1, true)), are not valid C# syntax.

Rendering BlockExpression in the Visual Basic renderer

For syntactic structures which expect a block of statements, the BlockExpression is rendered only as its individual statements:

'Imports System.Linq.Expressions.Expression
'Imports ExpressionToString

Dim writeline = GetType(Console).GetMethods("WriteLine", { })
Dim expr = IfThen(
    Constant(True),
    Block(
        [Call](writeline),
        [Call](writeline)
    )
)
Console.WriteLine(expr.ToString("Visual Basic"))
'
'    If True Then
'        Console.WriteLine
'        Console.WriteLine
'    End If
'

BlockExpressions in an inline position are rendered using (the non-existent) Block...End Block syntax:

'Imports System.Linq.Expressions.Expression
'Imports ExpressionToString

Dim writeline = GetType(Console).GetMethods("WriteLine", { })
Dim blockExpr = Block(
    Constant(True),
    Constant(True)
)
Dim expr = [AndAlso](
    blockExpr,
    blockExpr
)
Console.WriteLine(expr.ToString("Visual Basic"))
'
'    Block
'        True
'        True
'    End Block AndAlso Block
'        True
'        True
'    End Block
'

The statements in a nested BlockExpression are rendered without any indication of the BlockExpression, only alongside the parent statements:

Dim expr = IfThen(
    Constant(True),
    Block(
        Constant(True),
        Block(
            Constant(True),
            Constant(True)
        ),
        Constant(True)
    )
)
Console.WriteLine(expr.ToString("Visual Basic"))
'
'    If True Then
'        True
'        True
'        True
'        True
'    End If
'

unless local variables are introduced in the nested block, in which case the nested block is marked with Block....End Block:

Dim expr = IfThen(
    Constant(True),
    Block(
        Constant(True),
        Block(
            {Parameter(GetType(String), "s1")},
            Constant(True),
            Constant(True)
        ),
        Constant(True)
    )
)
Console.WriteLine(expr.ToString("Visual Basic"))
'
'    If True Then
'        True
'        Block
'            Dim s1 As String
'            True
'            True
'        End Block
'        True
'    End If
'