-
Notifications
You must be signed in to change notification settings - Fork 12
Home
Welcome to the wiki for the ExpressionTreeToString library. This library generates string representations of expression trees and related types:
Expression<Func<Person, bool>> expr = p => p.DOB.DayOfWeek == DayOfWeek.Tuesday;
Console.WriteLine(expr.ToString("C#"));
// prints:
/*
(Person p) => p.DOB.DayOfWeek == DayOfWeek.Tuesday
*/
Console.WriteLine(expr.ToString("Visual Basic"));
// prints:
/*
Function(p As Person) p.DOB.DayOfWeek = DayOfWeek.Tuesday
*/
There are two built-in string representations in .NET. One is the Expression.ToString method:
Console.WriteLine(expr.ToString());
/*
p => (Convert(p.DOB.DayOfWeek) == 2)
*/
However, the generated string has a number of limitations:
- conversions (
Convert(...)
vs(int)...
), - type names use the
Type.Name
property (List`1
vsList<string>
), - closed-over variables are rendered as members of a hidden class: (
value(sampleCode.Program+<>c__DisplayClass0_0).i
vsi
). - only C#-style
This is by design, per the docs, section 2.17:
Expression.ToString is for light weight debugging purposes only. ... ToString does not try to return semantically accurate C# or VB code in particular. We try to return terse strings loosely suggesting what an ET node contains for quick inspection only.
There is also the DebugView
property, which uses a special syntax to represent more of the expression tree's information:
.Lambda #Lambda1<System.Func`2[sampleCode.Person,System.Boolean]>(sampleCode.Person $p) {
(System.Int32)($p.DOB).DayOfWeek == 2
}
but the additional information is not always necessary, and can make it even harder to read. Also, unless you want to jump through some reflection hoops, you can only read this property while in a debugging session.
The string rendering library provides a set of .ToString
extension methods on the various types used in expression trees -- Expression
, CatchBlock
etc.. You pass in a string or enum value identifying which renderer you want to use (and optionally the language for rendering literals and type names).
For example:
Console.WriteLine(expr.ToString("Textual tree", Language.CSharp));
// prints:
/*
Lambda (Func<Person, bool>)
· Parameters[0] - Parameter (Person) p
· Body - Equal (bool)
· Left - Convert (int)
· Operand - MemberAccess (DayOfWeek) DayOfWeek
· Expression - MemberAccess (DateTime) DOB
· Expression - Parameter (Person) p
· Right - Constant (int) = 2
*/
Let's say you want to know which part of the rendering corresponds to the node at Body.Left.Operand
.
The .ToString
extension methods have an additional overload that takes an out Dictionary<string, (int start, int length)>
. Each key in the dictionary is the property path to an expression tree node from the root of the expression tree; the value is a tuple of the start and length of the corresponding substring.
For example, you could write the following:
string s = expr.ToString("C#", out Dictionary<string, (int start, int length)> pathSpans);
const int firstColumnAlignment = -45;
Console.WriteLine($"{"Path",firstColumnAlignment}Substring");
Console.WriteLine(new string('-', 85));
foreach (var kvp in pathSpans) {
var path = kvp.Key;
var (start, length) = kvp.Value;
Console.WriteLine(
$"{path,firstColumnAlignment}{new string(' ', start)}{s.Substring(start, length)}"
);
}
which would print:
Path Substring
-----------------------------------------------------------------------------------------------
Parameters[0] Person p
Body.Left.Operand.Expression.Expression p
Body.Left.Operand.Expression p.DOB
Body.Left.Operand p.DOB.DayOfWeek
Body.Right DayOfWeek.Tuesday
Body p.DOB.DayOfWeek == DayOfWeek.Tuesday
(Person p) => p.DOB.DayOfWeek == DayOfWeek.Tuesday
This tells us that the text corresponding to the object at Body.Left.Operand
is p.DOB.DayOfWeek
.
When using the non-language renderers (i.e. factory methods, object notation, or textual tree renderers) you can also specify the language for rendering literals, type names and other code constructs as "C#"
(the default) or "Visual Basic"
:
Console.WriteLine(expr.ToString("Factory methods", "Visual Basic"));
/*
' Imports System.Linq.Expressions.Expression
Lambda(
Call(
MakeMemberAccess(p,
GetType(Person).GetProperty("LastName")
),
GetType(String).GetMethod("StartsWith", { GetType(String) }),
Constant("A")
),
Dim p = Parameter(
GetType(Person),
"p"
)
)
*/
Literals are rendered depending on the value passed into the language
parameter, as described here.
Type names are rendered as described here.
Note that if you try to pass in the language
parameter together with one of the language renderers (C#
or Visual Basic
) it will be ignored.