String representations of expression trees + library of expression tree objects
Provides a ToString
extension method which returns a string representation of an expression tree (an object inheriting from System.Linq.Expressions.Expression
).
Expression<Func<bool>> expr = () => true;
Console.WriteLine(expr.ToString("C#"));
// prints: () => true
Console.WriteLine(expr.ToString("Visual Basic"));
// prints: Function() True
Console.WriteLine(expr.ToString("Factory methods", "C#"));
// prints:
/*
// using static System.Linq.Expressions.Expression
Lambda(
Constant(true)
)
*/
Console.WriteLine(expr.ToString("Object notation", "C#"));
// prints:
/*
new Expression<Func<bool>> {
NodeType = ExpressionType.Lambda,
Type = typeof(Func<bool>),
Body = new ConstantExpression {
Type = typeof(bool),
Value = true
},
ReturnType = typeof(bool)
}
*/
Console.WriteLine(expr.ToString("Object notation", "Visual Basic"));
// prints:
/*
New Expression(Of Func(Of Boolean)) With {
.NodeType = ExpressionType.Lambda,
.Type = GetType(Func(Of Boolean)),
.Body = New ConstantExpression With {
.Type = GetType(Boolean),
.Value = True
},
.ReturnType = GetType(Boolean)
}
*/
Console.WriteLine(expr.ToString("Textual tree"));
// prints:
/*
Lambda (Func<bool>)
Body - Constant (bool) = True
*/
var b = true;
Expression<Func<int>> expr1 = () => b ? 1 : 0;
Console.WriteLine(expr1.ToString("ToString"));
// prints:
/*
() => IIF(value(_tests.Program+<>c__DisplayClass0_0).b, 1, 0)
*/
Console.WriteLine(expr1.ToString("DebugView"));
// prints:
/*
.Lambda #Lambda1<System.Func`1[System.Int32]>() {
.If (
.Constant<_tests.Program+<>c__DisplayClass0_0>(_tests.Program+<>c__DisplayClass0_0).b
) {
1
} .Else {
0
}
}
*/
Expression<Func<Person, bool>> filter = p => p => p.LastName == "A" || p.FirstName == "B" || p.DOB == DateTime.MinValue || p.LastName == "C" || p.FirstName == "D";
Console.WriteLine(filter.ToString("Dynamic LINQ", "C#"));
// prints:
/*
"LastName in (\"A\", \"C\") || FirstName in (\"B\", \"D\") || DOB == DateTime.MinValue"
*/
Features:
Multiple writers:
For C# and VB pseudo-code representations:
Extension methods are rendered as instance methods
Expression<Func<int, int>> expr = x => Enumerable.Range(1, x).Select(y => x * y).Count();
Console.WriteLine(expr.ToString("C#"));
// prints: (int x) => Enumerable.Range(1, x).Select((int y) => x * y).Count()
Closed-over variables are rendered as simple identifiers (instead of member access on the hidden compiler-generated class)
var i = 7;
var j = 8;
Expression<Func<int>> expr = () => i + j;
Console.WriteLine(expr.ToString("C#"));
// prints: () => i + j
Calls to String.Concat
and String.Format
are mapped to the +
operator and string interpolation, respectively (where possible):
var name = "World";
Expression<Func<string>> expr = () => string.Format("Hello, {0}!", name);
Console.WriteLine(expr.ToString("C#"));
// prints: () => $"Hello, {name}!"
Unnecessary conversions are not rendered:
Expression<Func<IEnumerable<char>>> expr = () => (IEnumerable<char>)"abcd";
Console.WriteLine(expr.ToString("C#"));
// prints: () => "abcd"
Comparisons against an enum or char
are rendered properly, not as comparison to int
-converted value:
var dow = DayOfWeek.Sunday;
Expression<Func<bool>> expr = () => DateTime.Today.DayOfWeek == dow;
Console.WriteLine(expr.ToString("Textual tree", "C#"));
// prints:
/*
Lambda (Func<bool>)
· Body - Equal (bool) = false
· Left - Convert (int) = 3
· Operand - MemberAccess (DayOfWeek) DayOfWeek = DayOfWeek.Wednesday
· Expression - MemberAccess (DateTime) DateTime.Today = 30/09/2020 12:00:00 am
· Right - Convert (int) = 0
· Operand - MemberAccess (DayOfWeek) dow = DayOfWeek.Sunday
· Expression - Constant (<closure>) = #<closure>
*/
Console.WriteLine(expr.ToString("C#"));
// prints: () => DateTime.Today.DayOfWeek == dow
Each representation (including the ToString and DebugView renderers) can return the start and length of the substring corresponding to any of the paths of the tree's nodes, which can be used to find the substring corresponding to a given node in the tree:
var s = expr.ToString("C#", out var pathSpans);
Console.WriteLine(s);
// prints: (Person p) => p.DOB.DayOfWeek == DayOfWeek.Tuesday
(int start, int length) = pathSpans["Body.Left.Operand"];
Console.WriteLine(s.Substring(start, length));
// prints: p.DOB.DayOfWeek
Type names are rendered using language syntax and keywords, instead of the Type.Name property; e.g. List<string>
or List(Of Date)
instead of List`1
Supports the full range of types in System.Linq.Expressions
, including .NET 4 expression types, and DynamicExpression
Extensibility -- allows creating custom renderers, or inheriting from existing renderers, to handle your own Expression-derived types
For more information, see the wiki.