SlideShare a Scribd company logo
Oleksii Holub "Expression trees in C#"
Speaker: Alexey Golub @Tyrrrz
Expression Trees in C#
I heard you like code, so we put code in your code so you can code while you code
/whois ${speaker}
Speaker: Alexey Golub @Tyrrrz
• Open-source developer ✨
• Conference speaker & blogger 🌐️
• C#, F#, JavaScript 💻
• Cloud & web ☁️
• Automation & DevOps ⚙️
What is an expression tree?
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
+Constant (2) Constant (3)
Plus operator
2 3
Binary expression
Speaker: Alexey Golub @Tyrrrz
!string.IsNullOrWhiteSpace(personName)
? "Greetings, " + personName
: null;
string? GetGreeting(string personName)
{
return
}
Speaker: Alexey Golub @Tyrrrz
"Greetings, "
!
personName
string.IsNullOrWhiteSpace( )
null;:
? +
personName
OPERATOR "NOT" METHOD CALL
PARAMETER
PARAMETERCONSTANT OPERATOR "ADD"
CONSTANT
Speaker: Alexey Golub @Tyrrrz
{ Ternary conditional }
{ + }
TRUE
{ null }
FALSE
{ Method call }
CONDITION
{ string.IsNullOrWhiteSpace }
{ personName }
{ personName }
{ "Greetings, " }
{ ! }
Speaker: Alexey Golub @Tyrrrz
Expression Tree
describes the structure of an expression
Expression Trees in .NET
• Object model represented by types deriving from
System.Linq.Expressions.Expression
• Can be constructed manually using factory methods
• Can be inferred from lambdas by the compiler
• Lambda expression trees can be converted into delegates
Speaker: Alexey Golub @Tyrrrz
Constructing Expression
Trees Manually
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Expression.Constant(...) ConstantExpression
Expression.New(...) NewExpression
Expression.Assign(...) BinaryExpression
Expression.Equal(...) BinaryExpression
Expression.Call(...) MethodCallExpression
Expression.Condition(...) ConditionalExpression
Expression.Loop(...) LoopExpression
...
Speaker: Alexey Golub @Tyrrrz
!string.IsNullOrWhiteSpace(personName)
? "Greetings, " + personName
: null;
Let’s recreate our expression dynamically
Speaker: Alexey Golub @Tyrrrz
public Func<string, string?> ConstructGreetingFunction()
{
var personNameParameter = Expression.Parameter(typeof(string), "personName");
var isNullOrWhiteSpaceMethod = typeof(string)
.GetMethod(nameof(string.IsNullOrWhiteSpace));
var condition = Expression.Not(
Expression.Call(isNullOrWhiteSpaceMethod, personNameParameter));
var trueClause = Expression.Add(
Expression.Constant("Greetings, "),
personNameParameter);
var falseClause = Expression.Constant(null, typeof(string));
var conditional = Expression.Condition(condition, trueClause, falseClause);
var lambda = Expression.Lambda<Func<string, string?>>(conditional, personNameParameter);
return lambda.Compile();
}
Speaker: Alexey Golub @Tyrrrz
var getGreeting = ConstructGreetingFunction();
var greetingForJohn = getGreeting("John");
The binary operator Add is not defined for the types
'System.String' and 'System.String'.
Speaker: Alexey Golub @Tyrrrz
We need to call string.Concat() directly
var concatMethod = typeof(string)
.GetMethod(nameof(string.Concat), new[] {typeof(string), typeof(string)});
var trueClause = Expression.Call(
concatMethod,
Expression.Constant("Greetings, "),
personNameParameter);
Speaker: Alexey Golub @Tyrrrz
var getGreetings = ConstructGreetingFunction();
var greetingsForJohn = getGreetings("John");
var greetingsForNobody = getGreetings(" ");
// "Greetings, John"
// <null>
Optimizing Reflection-Heavy
Code
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
How can we invoke Execute() from the outside?
public class Command
{
private int Execute() /> 42;
}
public static int CallExecute(Command command) =>
(int) typeof(Command)
.GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(command, null);
Speaker: Alexey Golub @Tyrrrz
public static class ReflectionCached
{
private static MethodInfo ExecuteMethod { get; } = typeof(Command)
.GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.Instance);
public static int CallExecute(Command command) =>
(int) ExecuteMethod.Invoke(command, null);
}
public static class Re/lectionDelegate
{
private static MethodInfo ExecuteMethod { get; } = typeof(Command)
.GetMethod("Execute", Binding/lags.NonPublic | Binding/lags.Instance);
private static Func<Command, int> Impl { get; } =
(Func<Command, int>) Delegate
.CreateDelegate(typeof(Func<Command, int>), ExecuteMethod);
public static int CallExecute(Command command) /> Impl(command);
}
Using cached MethodInfo
Using Delegate.CreateDelegate
public static class ExpressionTrees
{
private static MethodInfo ExecuteMethod { get; } = typeof(Command)
.GetMethod("Execute", Binding/lags.NonPublic | Binding/lags.Instance);
private static Func<Command, int> Impl { get; }
static ExpressionTrees()
{
var instance = Expression.Parameter(typeof(Command));
var call = Expression.Call(instance, ExecuteMethod);
Impl = Expression.Lambda<Func<Command, int/>(call, instance).Compile();
}
public static int CallExecute(Command command) /> Impl(command);
}
Speaker: Alexey Golub @Tyrrrz
Lazy thread-safe
initialization via static
constructor
Speaker: Alexey Golub @Tyrrrz
public class Benchmarks
{
[Benchmark(Description = "Reflection", Baseline = true)]
public int Reflection() => (int) typeof(Command)
.GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(new Command(), null);
[Benchmark(Description = "Reflection (cached)")]
public int Cached() => ReflectionCached.CallExecute(new Command());
[Benchmark(Description = "Reflection (delegate)")]
public int Delegate() => ReflectionDelegate.CallExecute(new Command());
[Benchmark(Description = "Expressions")]
public int Expressions() => ExpressionTrees.CallExecute(new Command());
public static void Main() => BenchmarkRunner.Run<Benchmarks>();
}
| Method | Mean | Error | StdDev | Ratio |
|---------------------- |-----------:|----------:|----------:|------:|
| Reflection | 192.975 ns | 1.6802 ns | 1.4895 ns | 1.00 |
| Reflection (cached) | 123.762 ns | 1.1063 ns | 1.0349 ns | 0.64 |
| Reflection (delegate) | 6.419 ns | 0.0646 ns | 0.0605 ns | 0.03 |
| Expressions | 5.383 ns | 0.0433 ns | 0.0383 ns | 0.03 |
Implementing Generic
Operators
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
What can we do to support multiple types?
public int ThreeFourths(int x) => 3 * x / 4;
public int ThreeFourths(int x) => 3 * x / 4;
public long ThreeFourths(long x) => 3 * x / 4;
public float ThreeFourths(float x) => 3 * x / 4;
public double ThreeFourths(double x) => 3 * x / 4;
public decimal ThreeFourths(decimal x) => 3 * x / 4;
public T ThreeFourths<T>(T x) /> 3 * x / 4;
But we actually want something like this instead
Speaker: Alexey Golub @Tyrrrz
public T ThreeFourths<T>(T x)
{
var param = Expression.Parameter(typeof(T));
var three = Expression.Convert(Expression.Constant(3), typeof(T));
var four = Expression.Convert(Expression.Constant(4), typeof(T));
var operation = Expression.Divide(Expression.Multiply(param, three), four);
var lambda = Expression.Lambda<Func<T, T>>(operation, param);
var func = lambda.Compile();
return func(x);
}
var a = ThreeFourths(18); // 13
var b = ThreeFourths(6.66); // 4.995
var c = ThreeFourths(100M); // 75M
Speaker: Alexey Golub @Tyrrrz
public dynamic ThreeFourths(dynamic x) /> 3 * x / 4;
Wait, how is it different from this?
public static class ThreeFourths
{
private static class Impl<T>
{
public static Func<T, T> Of { get; }
static Impl()
{
var param = Expression.Parameter(typeof(T));
var three = Expression.Convert(Expression.Constant(3), typeof(T));
var four = Expression.Convert(Expression.Constant(4), typeof(T));
var operation = Expression.Divide(Expression.Multiply(param, three), four);
var lambda = Expression.Lambda<Func<T, T>>(operation, param);
Of = lambda.Compile();
}
}
public static T Of<T>(T x) => Impl<T>.Of(x);
}
Speaker: Alexey Golub @Tyrrrz
Generic, thread-safe
lazy initialization
Speaker: Alexey Golub @Tyrrrz
public class Benchmarks
{
[Benchmark(Description = "Static", Baseline = true)]
[Arguments(13.37)]
public double Static(double x) /> 3 * x / 4;
[Benchmark(Description = "Expressions")]
[Arguments(13.37)]
public double Expressions(double x) /> ThreeFourths.Of(x);
[Benchmark(Description = "Dynamic")]
[Arguments(13.37)]
public dynamic Dynamic(dynamic x) /> 3 * x / 4;
public static void Main() /> BenchmarkRunner.Run<Benchmarks>();
}
| Method | x | Mean | Error | StdDev | Ratio | RatioSD |
|------------ |------ |-----------:|----------:|----------:|------:|--------:|
| Static | 13.37 | 0.6077 ns | 0.0176 ns | 0.0147 ns | 1.00 | 0.00 |
| Expressions | 13.37 | 1.9510 ns | 0.0163 ns | 0.0145 ns | 3.21 | 0.08 |
| Dynamic | 13.37 | 19.3267 ns | 0.1512 ns | 0.1340 ns | 31.82 | 0.78 |
Parsing DSLs into Expression
Trees
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
public static class SimpleCalculator
{
private static readonly Parser<Expression> Constant =
Parse.DecimalInvariant
.Select(n => double.Parse(n, CultureInfo.InvariantCulture))
.Select(n => Expression.Constant(n, typeof(double)))
.Token();
private static readonly Parser<ExpressionType> Operator =
Parse.Char('+').Return(ExpressionType.Add)
.Or(Parse.Char('-').Return(ExpressionType.Subtract))
.Or(Parse.Char('*').Return(ExpressionType.Multiply))
.Or(Parse.Char('/').Return(ExpressionType.Divide));
private static readonly Parser<Expression> Operation =
Parse.ChainOperator(Operator, Constant, Expression.MakeBinary);
private static readonly Parser<Expression> FullExpression =
Operation.Or(Constant).End();
public static double Run(string expression)
{
var operation = FullExpression.Parse(expression);
var func = Expression.Lambda<Func<double>>(operation).Compile();
return func();
}
}
Speaker: Alexey Golub @Tyrrrz
var a = SimpleCalculator.Run("2 + 2");
var b = SimpleCalculator.Run("3.15 * 5 + 2");
var c = SimpleCalculator.Run("1 / 2 * 3");
// 4
// 17.75
// 1.5
Obtaining Expression Trees
from Lambdas
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Func<int, int, int> div =
(a, b) => a / b;
Expression<Func<int, int, int>> divExpr =
(a, b) => a / b;
Same value, different type
Console.WriteLine(divExpr.Type);
// System.Func`3[System.Int32,System.Int32,System.Int32]
Speaker: Alexey Golub @Tyrrrz
Func<int, int, int> div =
(a, b) /> a / b;
Expression<Func<int, int, int/> divExpr =
(a, b) /> a / b;
Same value, different type
foreach (var param in divExpr.Parameters)
Console.WriteLine($"Param: {param.Name} ({param.Type.Name})");
// Param: a (Int32)
// Param: b (Int32)
Speaker: Alexey Golub @Tyrrrz
Func<int, int, int> div =
(a, b) /> a / b;
Expression<Func<int, int, int/> divExpr =
(a, b) /> a / b;
Same value, different type
var div = divExpr.Compile();
var c = div(10, 2); // 5
Speaker: Alexey Golub @Tyrrrz
Func<int, int, int> div =
(a, b) => a / b;
Expression<Func<int, int, int>> divExpr =
(a, b) => a / b;
Product
Recipe
Speaker: Alexey Golub @Tyrrrz
Limitations
Func<int, int, int> div = (a, b) /> a / b;
Expression<Func<int, int, int/> divExpr = div;
Compilation error
Speaker: Alexey Golub @Tyrrrz
Limitations
Expression<Func<int, int, int/> divExpr = (a, b) />
{
var result = a / b;
return result;
};
Compilation error
Expression<Action> writeToConsole = () =>
{
Console.Write("Hello ");
Console.WriteLine("world!");
};
Compilation error
Speaker: Alexey Golub @Tyrrrz
Limitations
• Null-coalescing operator (obj?.Prop)
• Dynamic variables (dynamic)
• Asynchronous code (async/await)
• Default or named parameters (func(a, b: 5), func(a))
• Parameters passed by reference (int.TryParse("123", out var i))
• Multi-dimensional array initializers (new int[2, 2] { { 1, 2 }, { 3, 4 } })
• Assignment operations (a = 5)
• Increment and decrement (a++, a--, --a, ++a)
• Base type access (base.Prop)
• Dictionary initialization (new Dictionary<string, int> { ["foo"] = 100 })
• Unsafe code (via unsafe)
• Throw expressions (throw new Exception())
• Tuple literals ((5, x))
Can’t use any of the following:
Identifying Type Members
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
How can we get PropertyInfo of Dto.Id?
public class Dto
{
public Guid Id { get; set; }
public string Name { get; set; }
}
var idProperty = typeof(Dto).GetProperty(nameof(Dto.Id));
Console.WriteLine($"Type: {idProperty.DeclaringType.Name}");
Console.WriteLine($"Property: {idProperty.Name} ({idProperty.PropertyType.Name})");
// Type: Dto
// Property: Id (Guid)
Speaker: Alexey Golub @Tyrrrz
public class Validator<T>
{
// Add validation predicate to the list
public void AddValidation<TProp>(string propertyName, Func<TProp, bool> predicate)
{
var propertyInfo = typeof(T).GetProperty(propertyName);
if (propertyInfo is null)
throw new InvalidOperationException("Please provide a valid property name.");
// //.
}
// Evaluate all predicates
public bool Validate(T obj) { /* //. // }
/* //. //
}
var validator = new Validator<Dto>();
validator.AddValidation<Guid>(nameof(Dto.Id), id => id != Guid.Empty);
validator.AddValidation<string>(nameof(Dto.Name), name => !string.IsNullOrWhiteSpace(name));
var isValid = validator.Validate(new Dto { Id = Guid.NewGuid() }); // false
Speaker: Alexey Golub @Tyrrrz
What if we wanted to change the property type?
public class Dto
{
public int Id { get; set; }
public string Name { get; set; }
}
validator.AddValidation<Guid>(nameof(Dto.Id), id => id != Guid.Empty);
Still compiles, even though there is now an error
Speaker: Alexey Golub @Tyrrrz
public class Validator<T>
{
public void AddValidation<TProp>(
Expression<Func<T, TProp>> propertyExpression,
Func<TProp, bool> predicate)
{
var propertyInfo = (propertyExpression.Body as MemberExpression)?.Member as PropertyInfo;
if (propertyInfo is null)
throw new InvalidOperationException("Please provide a valid property expression.");
// ...
}
public bool Validate(T obj) { /* ... */ }
/* ... */
}
Expression is used to
identify a property
var validator = new Validator<Dto>();
validator.AddValidation(dto => dto.Id, id => id != Guid.Empty);
validator.AddValidation(dto => dto.Name, name => !string.IsNullOrWhiteSpace(name));
var isValid = validator.Validate(new Dto { Id = Guid.NewGuid() }); // false
Preserving Source Code in
Assertions
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
[Test]
public void IntTryParse_Test()
{
// Arrange
const string s = "123";
// Act
var result = int.TryParse(s, out var value);
// Assert
Assert.That(result, Is.True, "Parsing was unsuccessful");
Assert.That(value, Is.EqualTo(124), "Parsed value is incorrect");
}
X IntTryParse_Test [60ms]
Error Message:
Parsed value is incorrect
Expected: 124
But was: 123
Speaker: Alexey Golub @Tyrrrz
public static class AssertEx
{
public static void Express(Expression<Action> expression)
{
var act = expression.Compile();
try
{
act();
}
catch (AssertionException ex)
{
throw new AssertionException(
expression.Body.ToReadableString() +
Environment.NewLine +
ex.Message);
}
}
}
Extension method from
ReadableExpressions package
Speaker: Alexey Golub @Tyrrrz
X IntTryParse_Test [60ms]
Error Message:
Assert.That(value, Is.EqualTo(124))
Expected: 124
But was: 123
[Test]
public void IntTryParse_Test()
{
// Arrange
const string s = "123";
// Act
var result = int.TryParse(s, out var value);
// Assert
AssertEx.Express(() => Assert.That(result, Is.True));
AssertEx.Express(() => Assert.That(value, Is.EqualTo(124)));
}
Traversing and Rewriting
Expression Trees
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
public class Visitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
Console.WriteLine($"Visited method call: {node}");
return base.VisitMethodCall(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
Console.WriteLine($"Visited binary expression: {node}");
return base.VisitBinary(node);
}
}
Speaker: Alexey Golub @Tyrrrz
Expression<Func<double>> expr = () => Math.Sin(Guid.NewGuid().GetHashCode()) / 10;
new Visitor().Visit(expr);
// Visited binary expression: (Sin(Convert(NewGuid().GetHashCode(), Double)) / 10)
// Visited method call: Sin(Convert(NewGuid().GetHashCode(), Double))
// Visited method call: NewGuid().GetHashCode()
// Visited method call: NewGuid()
Speaker: Alexey Golub @Tyrrrz
public class Visitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
var newMethodCall = node.Method == typeof(Math).GetMethod(nameof(Math.Sin))
? typeof(Math).GetMethod(nameof(Math.Cos))
: node.Method;
return Expression.Call(newMethodCall, node.Arguments);
}
}
Speaker: Alexey Golub @Tyrrrz
Expression<Func<double/> expr = () /> Math.Sin(Guid.NewGuid().GetHashCode()) / 10;
var result = expr.Compile()();
Console.WriteLine($"Old expression: {expr.ToReadableString()}");
Console.WriteLine($"Old result: {result}");
var newExpr = (Expression<Func<double/>) new Visitor().Visit(expr);
var newResult = newExpr.Compile()();
Console.WriteLine($"New expression: {newExpr.ToReadableString()}");
Console.WriteLine($"New result value: {newResult}");
// Old expression: () => Math.Sin((double)Guid.NewGuid().GetHashCode()) / 10d
// Old result: 0.09489518488876232
// New expression: () => Math.Cos((double)Guid.NewGuid().GetHashCode()) / 10d
// New result value: 0.07306426748550407
Transpiling Code into a Different
Language
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
Expression<Action<int, int>> expr = (a, b) =>
Console.WriteLine("a + b = {0}", a + b));
var fsharpCode = FSharpTranspiler.Convert(expr);
Speaker: Alexey Golub @Tyrrrz
public static class FSharpTranspiler
{
private class Visitor : ExpressionVisitor
{
private readonly StringBuilder _buffer;
public Visitor(StringBuilder buffer)
{
_buffer = buffer;
}
// ...
}
public static string Convert<T>(Expression<T> expression)
{
var buffer = new StringBuilder();
new Visitor(buffer).Visit(expression);
return buffer.ToString();
}
}
Speaker: Alexey Golub @Tyrrrz
protected override Expression VisitLambda<T>(Expression<T> node)
{
_buffer.Append("fun (");
_buffer.AppendJoin(", ", node.Parameters.Select(p => p.Name));
_buffer.Append(") ->");
return base.VisitLambda(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(Console) &&
node.Method.Name == nameof(Console.WriteLine))
{
_buffer.Append("printfn ");
if (node.Arguments.Count > 1)
{
var format = (string) ((ConstantExpression) node.Arguments[0]).Value;
var formatValues = node.Arguments.Skip(1).ToArray();
_buffer.Append(""").Append(Regex.Replace(format, @"{d+}", "%O")).Append("" ");
_buffer.AppendJoin(" ", formatValues.Select(v => $"({v.ToReadableString()})"));
}
}
return base.VisitMethodCall(node);
}
Speaker: Alexey Golub @Tyrrrz
var fsharpCode = FSharpTranspiler.Convert<Action<int, int>>(
(a, b) => Console.WriteLine("a + b = {0}", a + b));
> let foo = fun (a, b) -> printfn "a + b = %O" (a + b)
val foo : a:int * b:int -> unit
> foo (3, 5)
a + b = 8
val it : unit = ()
// fun (a, b) > printfn "a + b = %O" (a + b)
Summary
• Expression trees are fun
• We can make reflection-heavy code much faster
• We can do late-binding with almost no performance penalties
• We can write our own runtime-compiled DSL
• We can provide refactor-safe identification for type members
• We can analyze specified lambdas and reflect on their structure
• We can rewrite existing expressions to behave differently
• We can transpile code into other languages
Speaker: Alexey Golub @Tyrrrz
Useful packages
• FastExpressionCompiler
https://github.com/dadhi/FastExpressionCompiler
Speeds up LambdaExpression.Compile()
• ReadableExpressions
https://github.com/agileobjects/ReadableExpressions
Converts expression to readable C# code
Speaker: Alexey Golub @Tyrrrz
Learn more
• Working with expression trees in C# (by me)
https://tyrrrz.me/blog/expression-trees
• Introduction to expression trees (MS docs)
https://docs.microsoft.com/en-us/dotnet/csharp/expression-trees
• Expression trees in enterprise software (Maksim Arshinov)
https://youtube.com/watch?v=J2XzsCoJM4o
Speaker: Alexey Golub @Tyrrrz
Thank you!
Speaker: Alexey Golub @Tyrrrz

More Related Content

PDF
Expression trees in C#
PDF
Expression trees in c#
PDF
tictactoe groovy
PDF
awesome groovy
PPTX
Expression trees in c#, Алексей Голубь (Svitla Systems)
PDF
groovy rules
PPT
Introduction To Groovy
PDF
functional groovy
Expression trees in C#
Expression trees in c#
tictactoe groovy
awesome groovy
Expression trees in c#, Алексей Голубь (Svitla Systems)
groovy rules
Introduction To Groovy
functional groovy

What's hot (20)

PDF
(Greach 2015) Dsl'ing your Groovy
PPTX
concurrency gpars
PDF
Designing with Groovy Traits - Gr8Conf India
PPTX
R. herves. clean code (theme)2
PPTX
python beginner talk slide
PDF
SeaJUG March 2004 - Groovy
ODP
Gpars concepts explained
PPT
Polyglot Programming in the JVM
PPTX
Kotlin as a Better Java
PPTX
Optimizing Tcl Bytecode
PDF
Logic programming a ruby perspective
PDF
Monadic Java
PDF
Implementing pseudo-keywords through Functional Programing
PPS
Making an Object System with Tcl 8.5
PDF
360|iDev
PDF
Monadic Java
PPT
Profiling and optimization
PDF
Design Pattern Observations
PDF
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
PPT
Functional Programming In Java
(Greach 2015) Dsl'ing your Groovy
concurrency gpars
Designing with Groovy Traits - Gr8Conf India
R. herves. clean code (theme)2
python beginner talk slide
SeaJUG March 2004 - Groovy
Gpars concepts explained
Polyglot Programming in the JVM
Kotlin as a Better Java
Optimizing Tcl Bytecode
Logic programming a ruby perspective
Monadic Java
Implementing pseudo-keywords through Functional Programing
Making an Object System with Tcl 8.5
360|iDev
Monadic Java
Profiling and optimization
Design Pattern Observations
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Functional Programming In Java
Ad

Similar to Oleksii Holub "Expression trees in C#" (20)

PDF
C# 7.x What's new and what's coming with C# 8
PDF
Clean Code 2
PDF
TypeScript Introduction
PDF
Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...
PPT
Clean code _v2003
PPTX
Clean Code
PDF
TypeScript Best Practices
PPT
Groovy Introduction - JAX Germany - 2008
PDF
C# - What's next
PDF
Kotlin+MicroProfile: Enseñando trucos de 20 años a un nuevo lenguaje
PDF
XAML/C# to HTML/JS
ODP
Ast transformations
PPTX
Java gets a closure
PPT
TechTalk - Dotnet
PDF
Refactoring to Macros with Clojure
DOCX
Bc0037
PPTX
Scala 3 Is Coming: Martin Odersky Shares What To Know
PPTX
Groovy
PDF
Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...
PPTX
Clojure And Swing
C# 7.x What's new and what's coming with C# 8
Clean Code 2
TypeScript Introduction
Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...
Clean code _v2003
Clean Code
TypeScript Best Practices
Groovy Introduction - JAX Germany - 2008
C# - What's next
Kotlin+MicroProfile: Enseñando trucos de 20 años a un nuevo lenguaje
XAML/C# to HTML/JS
Ast transformations
Java gets a closure
TechTalk - Dotnet
Refactoring to Macros with Clojure
Bc0037
Scala 3 Is Coming: Martin Odersky Shares What To Know
Groovy
Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...
Clojure And Swing
Ad

More from Fwdays (20)

PDF
"Mastering UI Complexity: State Machines and Reactive Patterns at Grammarly",...
PDF
"Effect, Fiber & Schema: tactical and technical characteristics of Effect.ts"...
PPTX
"Computer Use Agents: From SFT to Classic RL", Maksym Shamrai
PPTX
"Як ми переписали Сільпо на Angular", Євген Русаков
PDF
"AI Transformation: Directions and Challenges", Pavlo Shaternik
PDF
"Validation and Observability of AI Agents", Oleksandr Denisyuk
PPTX
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
PDF
"Beyond English: Navigating the Challenges of Building a Ukrainian-language R...
PPTX
"Co-Authoring with a Machine: What I Learned from Writing a Book on Generativ...
PPTX
"Human-AI Collaboration Models for Better Decisions, Faster Workflows, and Cr...
PDF
"AI is already here. What will happen to your team (and your role) tomorrow?"...
PPTX
"Is it worth investing in AI in 2025?", Alexander Sharko
PDF
''Taming Explosive Growth: Building Resilience in a Hyper-Scaled Financial Pl...
PDF
"Scaling in space and time with Temporal", Andriy Lupa.pdf
PDF
"Database isolation: how we deal with hundreds of direct connections to the d...
PDF
"Scaling in space and time with Temporal", Andriy Lupa .pdf
PPTX
"Provisioning via DOT-Chain: from catering to drone marketplaces", Volodymyr ...
PPTX
" Observability with Elasticsearch: Best Practices for High-Load Platform", A...
PPTX
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
PPTX
"Istio Ambient Mesh in production: our way from Sidecar to Sidecar-less",Hlib...
"Mastering UI Complexity: State Machines and Reactive Patterns at Grammarly",...
"Effect, Fiber & Schema: tactical and technical characteristics of Effect.ts"...
"Computer Use Agents: From SFT to Classic RL", Maksym Shamrai
"Як ми переписали Сільпо на Angular", Євген Русаков
"AI Transformation: Directions and Challenges", Pavlo Shaternik
"Validation and Observability of AI Agents", Oleksandr Denisyuk
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
"Beyond English: Navigating the Challenges of Building a Ukrainian-language R...
"Co-Authoring with a Machine: What I Learned from Writing a Book on Generativ...
"Human-AI Collaboration Models for Better Decisions, Faster Workflows, and Cr...
"AI is already here. What will happen to your team (and your role) tomorrow?"...
"Is it worth investing in AI in 2025?", Alexander Sharko
''Taming Explosive Growth: Building Resilience in a Hyper-Scaled Financial Pl...
"Scaling in space and time with Temporal", Andriy Lupa.pdf
"Database isolation: how we deal with hundreds of direct connections to the d...
"Scaling in space and time with Temporal", Andriy Lupa .pdf
"Provisioning via DOT-Chain: from catering to drone marketplaces", Volodymyr ...
" Observability with Elasticsearch: Best Practices for High-Load Platform", A...
"How to survive Black Friday: preparing e-commerce for a peak season", Yurii ...
"Istio Ambient Mesh in production: our way from Sidecar to Sidecar-less",Hlib...

Recently uploaded (20)

PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Machine learning based COVID-19 study performance prediction
DOCX
The AUB Centre for AI in Media Proposal.docx
PPTX
Big Data Technologies - Introduction.pptx
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PPT
Teaching material agriculture food technology
PDF
cuic standard and advanced reporting.pdf
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Encapsulation theory and applications.pdf
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
KodekX | Application Modernization Development
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
“AI and Expert System Decision Support & Business Intelligence Systems”
Review of recent advances in non-invasive hemoglobin estimation
Unlocking AI with Model Context Protocol (MCP)
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Machine learning based COVID-19 study performance prediction
The AUB Centre for AI in Media Proposal.docx
Big Data Technologies - Introduction.pptx
Understanding_Digital_Forensics_Presentation.pptx
Teaching material agriculture food technology
cuic standard and advanced reporting.pdf
Advanced methodologies resolving dimensionality complications for autism neur...
Spectral efficient network and resource selection model in 5G networks
Encapsulation theory and applications.pdf
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
KodekX | Application Modernization Development
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Mobile App Security Testing_ A Comprehensive Guide.pdf
Network Security Unit 5.pdf for BCA BBA.
Dropbox Q2 2025 Financial Results & Investor Presentation

Oleksii Holub "Expression trees in C#"

  • 2. Speaker: Alexey Golub @Tyrrrz Expression Trees in C# I heard you like code, so we put code in your code so you can code while you code
  • 3. /whois ${speaker} Speaker: Alexey Golub @Tyrrrz • Open-source developer ✨ • Conference speaker & blogger 🌐️ • C#, F#, JavaScript 💻 • Cloud & web ☁️ • Automation & DevOps ⚙️
  • 4. What is an expression tree? Speaker: Alexey Golub @Tyrrrz
  • 5. Speaker: Alexey Golub @Tyrrrz +Constant (2) Constant (3) Plus operator 2 3 Binary expression
  • 6. Speaker: Alexey Golub @Tyrrrz !string.IsNullOrWhiteSpace(personName) ? "Greetings, " + personName : null; string? GetGreeting(string personName) { return }
  • 7. Speaker: Alexey Golub @Tyrrrz "Greetings, " ! personName string.IsNullOrWhiteSpace( ) null;: ? + personName OPERATOR "NOT" METHOD CALL PARAMETER PARAMETERCONSTANT OPERATOR "ADD" CONSTANT
  • 8. Speaker: Alexey Golub @Tyrrrz { Ternary conditional } { + } TRUE { null } FALSE { Method call } CONDITION { string.IsNullOrWhiteSpace } { personName } { personName } { "Greetings, " } { ! }
  • 9. Speaker: Alexey Golub @Tyrrrz Expression Tree describes the structure of an expression
  • 10. Expression Trees in .NET • Object model represented by types deriving from System.Linq.Expressions.Expression • Can be constructed manually using factory methods • Can be inferred from lambdas by the compiler • Lambda expression trees can be converted into delegates Speaker: Alexey Golub @Tyrrrz
  • 13. Speaker: Alexey Golub @Tyrrrz Expression.Constant(...) ConstantExpression Expression.New(...) NewExpression Expression.Assign(...) BinaryExpression Expression.Equal(...) BinaryExpression Expression.Call(...) MethodCallExpression Expression.Condition(...) ConditionalExpression Expression.Loop(...) LoopExpression ...
  • 14. Speaker: Alexey Golub @Tyrrrz !string.IsNullOrWhiteSpace(personName) ? "Greetings, " + personName : null; Let’s recreate our expression dynamically
  • 15. Speaker: Alexey Golub @Tyrrrz public Func<string, string?> ConstructGreetingFunction() { var personNameParameter = Expression.Parameter(typeof(string), "personName"); var isNullOrWhiteSpaceMethod = typeof(string) .GetMethod(nameof(string.IsNullOrWhiteSpace)); var condition = Expression.Not( Expression.Call(isNullOrWhiteSpaceMethod, personNameParameter)); var trueClause = Expression.Add( Expression.Constant("Greetings, "), personNameParameter); var falseClause = Expression.Constant(null, typeof(string)); var conditional = Expression.Condition(condition, trueClause, falseClause); var lambda = Expression.Lambda<Func<string, string?>>(conditional, personNameParameter); return lambda.Compile(); }
  • 16. Speaker: Alexey Golub @Tyrrrz var getGreeting = ConstructGreetingFunction(); var greetingForJohn = getGreeting("John"); The binary operator Add is not defined for the types 'System.String' and 'System.String'.
  • 17. Speaker: Alexey Golub @Tyrrrz We need to call string.Concat() directly var concatMethod = typeof(string) .GetMethod(nameof(string.Concat), new[] {typeof(string), typeof(string)}); var trueClause = Expression.Call( concatMethod, Expression.Constant("Greetings, "), personNameParameter);
  • 18. Speaker: Alexey Golub @Tyrrrz var getGreetings = ConstructGreetingFunction(); var greetingsForJohn = getGreetings("John"); var greetingsForNobody = getGreetings(" "); // "Greetings, John" // <null>
  • 20. Speaker: Alexey Golub @Tyrrrz How can we invoke Execute() from the outside? public class Command { private int Execute() /> 42; } public static int CallExecute(Command command) => (int) typeof(Command) .GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.Instance) .Invoke(command, null);
  • 21. Speaker: Alexey Golub @Tyrrrz public static class ReflectionCached { private static MethodInfo ExecuteMethod { get; } = typeof(Command) .GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.Instance); public static int CallExecute(Command command) => (int) ExecuteMethod.Invoke(command, null); } public static class Re/lectionDelegate { private static MethodInfo ExecuteMethod { get; } = typeof(Command) .GetMethod("Execute", Binding/lags.NonPublic | Binding/lags.Instance); private static Func<Command, int> Impl { get; } = (Func<Command, int>) Delegate .CreateDelegate(typeof(Func<Command, int>), ExecuteMethod); public static int CallExecute(Command command) /> Impl(command); } Using cached MethodInfo Using Delegate.CreateDelegate
  • 22. public static class ExpressionTrees { private static MethodInfo ExecuteMethod { get; } = typeof(Command) .GetMethod("Execute", Binding/lags.NonPublic | Binding/lags.Instance); private static Func<Command, int> Impl { get; } static ExpressionTrees() { var instance = Expression.Parameter(typeof(Command)); var call = Expression.Call(instance, ExecuteMethod); Impl = Expression.Lambda<Func<Command, int/>(call, instance).Compile(); } public static int CallExecute(Command command) /> Impl(command); } Speaker: Alexey Golub @Tyrrrz Lazy thread-safe initialization via static constructor
  • 23. Speaker: Alexey Golub @Tyrrrz public class Benchmarks { [Benchmark(Description = "Reflection", Baseline = true)] public int Reflection() => (int) typeof(Command) .GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.Instance) .Invoke(new Command(), null); [Benchmark(Description = "Reflection (cached)")] public int Cached() => ReflectionCached.CallExecute(new Command()); [Benchmark(Description = "Reflection (delegate)")] public int Delegate() => ReflectionDelegate.CallExecute(new Command()); [Benchmark(Description = "Expressions")] public int Expressions() => ExpressionTrees.CallExecute(new Command()); public static void Main() => BenchmarkRunner.Run<Benchmarks>(); } | Method | Mean | Error | StdDev | Ratio | |---------------------- |-----------:|----------:|----------:|------:| | Reflection | 192.975 ns | 1.6802 ns | 1.4895 ns | 1.00 | | Reflection (cached) | 123.762 ns | 1.1063 ns | 1.0349 ns | 0.64 | | Reflection (delegate) | 6.419 ns | 0.0646 ns | 0.0605 ns | 0.03 | | Expressions | 5.383 ns | 0.0433 ns | 0.0383 ns | 0.03 |
  • 25. Speaker: Alexey Golub @Tyrrrz What can we do to support multiple types? public int ThreeFourths(int x) => 3 * x / 4; public int ThreeFourths(int x) => 3 * x / 4; public long ThreeFourths(long x) => 3 * x / 4; public float ThreeFourths(float x) => 3 * x / 4; public double ThreeFourths(double x) => 3 * x / 4; public decimal ThreeFourths(decimal x) => 3 * x / 4; public T ThreeFourths<T>(T x) /> 3 * x / 4; But we actually want something like this instead
  • 26. Speaker: Alexey Golub @Tyrrrz public T ThreeFourths<T>(T x) { var param = Expression.Parameter(typeof(T)); var three = Expression.Convert(Expression.Constant(3), typeof(T)); var four = Expression.Convert(Expression.Constant(4), typeof(T)); var operation = Expression.Divide(Expression.Multiply(param, three), four); var lambda = Expression.Lambda<Func<T, T>>(operation, param); var func = lambda.Compile(); return func(x); } var a = ThreeFourths(18); // 13 var b = ThreeFourths(6.66); // 4.995 var c = ThreeFourths(100M); // 75M
  • 27. Speaker: Alexey Golub @Tyrrrz public dynamic ThreeFourths(dynamic x) /> 3 * x / 4; Wait, how is it different from this?
  • 28. public static class ThreeFourths { private static class Impl<T> { public static Func<T, T> Of { get; } static Impl() { var param = Expression.Parameter(typeof(T)); var three = Expression.Convert(Expression.Constant(3), typeof(T)); var four = Expression.Convert(Expression.Constant(4), typeof(T)); var operation = Expression.Divide(Expression.Multiply(param, three), four); var lambda = Expression.Lambda<Func<T, T>>(operation, param); Of = lambda.Compile(); } } public static T Of<T>(T x) => Impl<T>.Of(x); } Speaker: Alexey Golub @Tyrrrz Generic, thread-safe lazy initialization
  • 29. Speaker: Alexey Golub @Tyrrrz public class Benchmarks { [Benchmark(Description = "Static", Baseline = true)] [Arguments(13.37)] public double Static(double x) /> 3 * x / 4; [Benchmark(Description = "Expressions")] [Arguments(13.37)] public double Expressions(double x) /> ThreeFourths.Of(x); [Benchmark(Description = "Dynamic")] [Arguments(13.37)] public dynamic Dynamic(dynamic x) /> 3 * x / 4; public static void Main() /> BenchmarkRunner.Run<Benchmarks>(); } | Method | x | Mean | Error | StdDev | Ratio | RatioSD | |------------ |------ |-----------:|----------:|----------:|------:|--------:| | Static | 13.37 | 0.6077 ns | 0.0176 ns | 0.0147 ns | 1.00 | 0.00 | | Expressions | 13.37 | 1.9510 ns | 0.0163 ns | 0.0145 ns | 3.21 | 0.08 | | Dynamic | 13.37 | 19.3267 ns | 0.1512 ns | 0.1340 ns | 31.82 | 0.78 |
  • 30. Parsing DSLs into Expression Trees Speaker: Alexey Golub @Tyrrrz
  • 31. Speaker: Alexey Golub @Tyrrrz public static class SimpleCalculator { private static readonly Parser<Expression> Constant = Parse.DecimalInvariant .Select(n => double.Parse(n, CultureInfo.InvariantCulture)) .Select(n => Expression.Constant(n, typeof(double))) .Token(); private static readonly Parser<ExpressionType> Operator = Parse.Char('+').Return(ExpressionType.Add) .Or(Parse.Char('-').Return(ExpressionType.Subtract)) .Or(Parse.Char('*').Return(ExpressionType.Multiply)) .Or(Parse.Char('/').Return(ExpressionType.Divide)); private static readonly Parser<Expression> Operation = Parse.ChainOperator(Operator, Constant, Expression.MakeBinary); private static readonly Parser<Expression> FullExpression = Operation.Or(Constant).End(); public static double Run(string expression) { var operation = FullExpression.Parse(expression); var func = Expression.Lambda<Func<double>>(operation).Compile(); return func(); } }
  • 32. Speaker: Alexey Golub @Tyrrrz var a = SimpleCalculator.Run("2 + 2"); var b = SimpleCalculator.Run("3.15 * 5 + 2"); var c = SimpleCalculator.Run("1 / 2 * 3"); // 4 // 17.75 // 1.5
  • 33. Obtaining Expression Trees from Lambdas Speaker: Alexey Golub @Tyrrrz
  • 34. Speaker: Alexey Golub @Tyrrrz Func<int, int, int> div = (a, b) => a / b; Expression<Func<int, int, int>> divExpr = (a, b) => a / b; Same value, different type Console.WriteLine(divExpr.Type); // System.Func`3[System.Int32,System.Int32,System.Int32]
  • 35. Speaker: Alexey Golub @Tyrrrz Func<int, int, int> div = (a, b) /> a / b; Expression<Func<int, int, int/> divExpr = (a, b) /> a / b; Same value, different type foreach (var param in divExpr.Parameters) Console.WriteLine($"Param: {param.Name} ({param.Type.Name})"); // Param: a (Int32) // Param: b (Int32)
  • 36. Speaker: Alexey Golub @Tyrrrz Func<int, int, int> div = (a, b) /> a / b; Expression<Func<int, int, int/> divExpr = (a, b) /> a / b; Same value, different type var div = divExpr.Compile(); var c = div(10, 2); // 5
  • 37. Speaker: Alexey Golub @Tyrrrz Func<int, int, int> div = (a, b) => a / b; Expression<Func<int, int, int>> divExpr = (a, b) => a / b; Product Recipe
  • 38. Speaker: Alexey Golub @Tyrrrz Limitations Func<int, int, int> div = (a, b) /> a / b; Expression<Func<int, int, int/> divExpr = div; Compilation error
  • 39. Speaker: Alexey Golub @Tyrrrz Limitations Expression<Func<int, int, int/> divExpr = (a, b) /> { var result = a / b; return result; }; Compilation error Expression<Action> writeToConsole = () => { Console.Write("Hello "); Console.WriteLine("world!"); }; Compilation error
  • 40. Speaker: Alexey Golub @Tyrrrz Limitations • Null-coalescing operator (obj?.Prop) • Dynamic variables (dynamic) • Asynchronous code (async/await) • Default or named parameters (func(a, b: 5), func(a)) • Parameters passed by reference (int.TryParse("123", out var i)) • Multi-dimensional array initializers (new int[2, 2] { { 1, 2 }, { 3, 4 } }) • Assignment operations (a = 5) • Increment and decrement (a++, a--, --a, ++a) • Base type access (base.Prop) • Dictionary initialization (new Dictionary<string, int> { ["foo"] = 100 }) • Unsafe code (via unsafe) • Throw expressions (throw new Exception()) • Tuple literals ((5, x)) Can’t use any of the following:
  • 41. Identifying Type Members Speaker: Alexey Golub @Tyrrrz
  • 42. Speaker: Alexey Golub @Tyrrrz How can we get PropertyInfo of Dto.Id? public class Dto { public Guid Id { get; set; } public string Name { get; set; } } var idProperty = typeof(Dto).GetProperty(nameof(Dto.Id)); Console.WriteLine($"Type: {idProperty.DeclaringType.Name}"); Console.WriteLine($"Property: {idProperty.Name} ({idProperty.PropertyType.Name})"); // Type: Dto // Property: Id (Guid)
  • 43. Speaker: Alexey Golub @Tyrrrz public class Validator<T> { // Add validation predicate to the list public void AddValidation<TProp>(string propertyName, Func<TProp, bool> predicate) { var propertyInfo = typeof(T).GetProperty(propertyName); if (propertyInfo is null) throw new InvalidOperationException("Please provide a valid property name."); // //. } // Evaluate all predicates public bool Validate(T obj) { /* //. // } /* //. // } var validator = new Validator<Dto>(); validator.AddValidation<Guid>(nameof(Dto.Id), id => id != Guid.Empty); validator.AddValidation<string>(nameof(Dto.Name), name => !string.IsNullOrWhiteSpace(name)); var isValid = validator.Validate(new Dto { Id = Guid.NewGuid() }); // false
  • 44. Speaker: Alexey Golub @Tyrrrz What if we wanted to change the property type? public class Dto { public int Id { get; set; } public string Name { get; set; } } validator.AddValidation<Guid>(nameof(Dto.Id), id => id != Guid.Empty); Still compiles, even though there is now an error
  • 45. Speaker: Alexey Golub @Tyrrrz public class Validator<T> { public void AddValidation<TProp>( Expression<Func<T, TProp>> propertyExpression, Func<TProp, bool> predicate) { var propertyInfo = (propertyExpression.Body as MemberExpression)?.Member as PropertyInfo; if (propertyInfo is null) throw new InvalidOperationException("Please provide a valid property expression."); // ... } public bool Validate(T obj) { /* ... */ } /* ... */ } Expression is used to identify a property var validator = new Validator<Dto>(); validator.AddValidation(dto => dto.Id, id => id != Guid.Empty); validator.AddValidation(dto => dto.Name, name => !string.IsNullOrWhiteSpace(name)); var isValid = validator.Validate(new Dto { Id = Guid.NewGuid() }); // false
  • 46. Preserving Source Code in Assertions Speaker: Alexey Golub @Tyrrrz
  • 47. Speaker: Alexey Golub @Tyrrrz [Test] public void IntTryParse_Test() { // Arrange const string s = "123"; // Act var result = int.TryParse(s, out var value); // Assert Assert.That(result, Is.True, "Parsing was unsuccessful"); Assert.That(value, Is.EqualTo(124), "Parsed value is incorrect"); } X IntTryParse_Test [60ms] Error Message: Parsed value is incorrect Expected: 124 But was: 123
  • 48. Speaker: Alexey Golub @Tyrrrz public static class AssertEx { public static void Express(Expression<Action> expression) { var act = expression.Compile(); try { act(); } catch (AssertionException ex) { throw new AssertionException( expression.Body.ToReadableString() + Environment.NewLine + ex.Message); } } } Extension method from ReadableExpressions package
  • 49. Speaker: Alexey Golub @Tyrrrz X IntTryParse_Test [60ms] Error Message: Assert.That(value, Is.EqualTo(124)) Expected: 124 But was: 123 [Test] public void IntTryParse_Test() { // Arrange const string s = "123"; // Act var result = int.TryParse(s, out var value); // Assert AssertEx.Express(() => Assert.That(result, Is.True)); AssertEx.Express(() => Assert.That(value, Is.EqualTo(124))); }
  • 50. Traversing and Rewriting Expression Trees Speaker: Alexey Golub @Tyrrrz
  • 52. Speaker: Alexey Golub @Tyrrrz public class Visitor : ExpressionVisitor { protected override Expression VisitMethodCall(MethodCallExpression node) { Console.WriteLine($"Visited method call: {node}"); return base.VisitMethodCall(node); } protected override Expression VisitBinary(BinaryExpression node) { Console.WriteLine($"Visited binary expression: {node}"); return base.VisitBinary(node); } }
  • 53. Speaker: Alexey Golub @Tyrrrz Expression<Func<double>> expr = () => Math.Sin(Guid.NewGuid().GetHashCode()) / 10; new Visitor().Visit(expr); // Visited binary expression: (Sin(Convert(NewGuid().GetHashCode(), Double)) / 10) // Visited method call: Sin(Convert(NewGuid().GetHashCode(), Double)) // Visited method call: NewGuid().GetHashCode() // Visited method call: NewGuid()
  • 54. Speaker: Alexey Golub @Tyrrrz public class Visitor : ExpressionVisitor { protected override Expression VisitMethodCall(MethodCallExpression node) { var newMethodCall = node.Method == typeof(Math).GetMethod(nameof(Math.Sin)) ? typeof(Math).GetMethod(nameof(Math.Cos)) : node.Method; return Expression.Call(newMethodCall, node.Arguments); } }
  • 55. Speaker: Alexey Golub @Tyrrrz Expression<Func<double/> expr = () /> Math.Sin(Guid.NewGuid().GetHashCode()) / 10; var result = expr.Compile()(); Console.WriteLine($"Old expression: {expr.ToReadableString()}"); Console.WriteLine($"Old result: {result}"); var newExpr = (Expression<Func<double/>) new Visitor().Visit(expr); var newResult = newExpr.Compile()(); Console.WriteLine($"New expression: {newExpr.ToReadableString()}"); Console.WriteLine($"New result value: {newResult}"); // Old expression: () => Math.Sin((double)Guid.NewGuid().GetHashCode()) / 10d // Old result: 0.09489518488876232 // New expression: () => Math.Cos((double)Guid.NewGuid().GetHashCode()) / 10d // New result value: 0.07306426748550407
  • 56. Transpiling Code into a Different Language Speaker: Alexey Golub @Tyrrrz
  • 57. Speaker: Alexey Golub @Tyrrrz Expression<Action<int, int>> expr = (a, b) => Console.WriteLine("a + b = {0}", a + b)); var fsharpCode = FSharpTranspiler.Convert(expr);
  • 58. Speaker: Alexey Golub @Tyrrrz public static class FSharpTranspiler { private class Visitor : ExpressionVisitor { private readonly StringBuilder _buffer; public Visitor(StringBuilder buffer) { _buffer = buffer; } // ... } public static string Convert<T>(Expression<T> expression) { var buffer = new StringBuilder(); new Visitor(buffer).Visit(expression); return buffer.ToString(); } }
  • 59. Speaker: Alexey Golub @Tyrrrz protected override Expression VisitLambda<T>(Expression<T> node) { _buffer.Append("fun ("); _buffer.AppendJoin(", ", node.Parameters.Select(p => p.Name)); _buffer.Append(") ->"); return base.VisitLambda(node); } protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.DeclaringType == typeof(Console) && node.Method.Name == nameof(Console.WriteLine)) { _buffer.Append("printfn "); if (node.Arguments.Count > 1) { var format = (string) ((ConstantExpression) node.Arguments[0]).Value; var formatValues = node.Arguments.Skip(1).ToArray(); _buffer.Append(""").Append(Regex.Replace(format, @"{d+}", "%O")).Append("" "); _buffer.AppendJoin(" ", formatValues.Select(v => $"({v.ToReadableString()})")); } } return base.VisitMethodCall(node); }
  • 60. Speaker: Alexey Golub @Tyrrrz var fsharpCode = FSharpTranspiler.Convert<Action<int, int>>( (a, b) => Console.WriteLine("a + b = {0}", a + b)); > let foo = fun (a, b) -> printfn "a + b = %O" (a + b) val foo : a:int * b:int -> unit > foo (3, 5) a + b = 8 val it : unit = () // fun (a, b) > printfn "a + b = %O" (a + b)
  • 61. Summary • Expression trees are fun • We can make reflection-heavy code much faster • We can do late-binding with almost no performance penalties • We can write our own runtime-compiled DSL • We can provide refactor-safe identification for type members • We can analyze specified lambdas and reflect on their structure • We can rewrite existing expressions to behave differently • We can transpile code into other languages Speaker: Alexey Golub @Tyrrrz
  • 62. Useful packages • FastExpressionCompiler https://github.com/dadhi/FastExpressionCompiler Speeds up LambdaExpression.Compile() • ReadableExpressions https://github.com/agileobjects/ReadableExpressions Converts expression to readable C# code Speaker: Alexey Golub @Tyrrrz
  • 63. Learn more • Working with expression trees in C# (by me) https://tyrrrz.me/blog/expression-trees • Introduction to expression trees (MS docs) https://docs.microsoft.com/en-us/dotnet/csharp/expression-trees • Expression trees in enterprise software (Maksim Arshinov) https://youtube.com/watch?v=J2XzsCoJM4o Speaker: Alexey Golub @Tyrrrz
  • 64. Thank you! Speaker: Alexey Golub @Tyrrrz