SlideShare a Scribd company logo
1 of 72
Download to read offline
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
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>
Not everything is an expression
Speaker: Alexey Golub @Tyrrrz
but we are not limited by that
Speaker: Alexey Golub @Tyrrrz
new StringBuilder()
.Append("Hello ")
.AppendLine("world!");
Statements
Expression
Console.Write("Hello ");
Console.WriteLine("world!");
Speaker: Alexey Golub @Tyrrrz
public Expression CreateStatementBlock()
{
var consoleWriteMethod = typeof(Console)
.GetMethod(nameof(Console.Write), new[] {typeof(string)});
var consoleWriteLineMethod = typeof(Console)
.GetMethod(nameof(Console.WriteLine), new[] {typeof(string)});
return Expression.Block(
Expression.Call(consoleWriteMethod, Expression.Constant("Hello ")),
Expression.Call(consoleWriteLineMethod, Expression.Constant("world!")));
}
var block = CreateStatementBlock();
var lambda = Expression.Lambda<Action>(block).Compile();
lambda(); // Hello world!
Speaker: Alexey Golub @Tyrrrz
public Expression CreateStatementBlock()
{
var variableA = Expression.Variable(typeof(string), "a");
var variableB = Expression.Variable(typeof(string), "b");
return Expression.Block(
new[] {variableA, variableB},
Expression.Assign(variableA, Expression.Constant("Foo ")),
Expression.Assign(variableB, Expression.Constant("bar")),
Expression.Call(consoleWriteMethod, variableA),
Expression.Call(consoleWriteLineMethod, variableB));
}
Declare variables
Assign values to
variables
Reference variables
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", Binding/lags.NonPublic | Binding/lags.Instance)
.Invoke(command, null);
Speaker: Alexey Golub @Tyrrrz
public static class Re/lectionCached
{
private static MethodInfo ExecuteMethod { get; } = typeof(Command)
.GetMethod("Execute", Binding/lags.NonPublic | Binding/lags.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 |
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 |
Compiled dictionary
Speaker: Alexey Golub @Tyrrrz
Speaker: Alexey Golub @Tyrrrz
public TValue Lookup(TKey key) => key.GetHashCode() switch
{
// No collisions
9254 => value1,
-101 => value2,
// Collision
777 => key switch
{
key3 => value3,
key4 => value4
},
// ...
// Not found
_ => throw new KeyNotFoundException(key.ToString())
};
Dictionary lookup in a nutshell
Speaker: Alexey Golub @Tyrrrz
public class CompiledDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly IDictionary<TKey, TValue> _inner = new Dictionary<TKey, TValue>();
private Func<TKey, TValue> _lookup;
public void UpdateLookup()
{
// //.
}
public TValue this[TKey key]
{
get /> _lookup(key);
set /> _inner[key] = value;
}
// The rest of the interface implementation is omitted for brevity
}
Speaker: Alexey Golub @Tyrrrz
public void UpdateLookup()
{
var keyParameter = Expression.Parameter(typeof(TKey));
var keyGetHashCodeCall = Expression.Call(
keyParameter,
typeof(object).GetMethod(nameof(GetHashCode)));
var keyToStringCall = Expression.Call(
keyParameter,
typeof(object).GetMethod(nameof(ToString)));
var exceptionCtor = typeof(KeyNotFoundException)
.GetConstructor(new[] {typeof(string)});
var throwException = Expression.Throw(
Expression.New(exceptionCtor, keyToStringCall),
typeof(TValue));
var body = Expression.Switch(
// ...
));
var lambda = Expression.Lambda<Func<TKey, TValue>>(body, keyParameter);
_lookup = lambda.Compile();
}
Speaker: Alexey Golub @Tyrrrz
var body = Expression.Switch(
typeof(TValue),
keyGetHashCodeCall,
throwException,
null,
_inner
.GroupBy(p /> p.Key.GetHashCode())
.Select(g />
{
if (g.Count() /= 1)
return Expression.SwitchCase(
Expression.Constant(g.Single().Value),
Expression.Constant(g.Key));
return Expression.SwitchCase(
Expression.Switch(
typeof(TValue),
keyParameter,
throwException,
null,
g.Select(p /> Expression.SwitchCase(
Expression.Constant(p.Value),
Expression.Constant(p.Key)
))),
Expression.Constant(g.Key));
}));
No collision
Collision
Speaker: Alexey Golub @Tyrrrz
| Method | Count | Mean | Error | StdDev | Ratio |
|-------------------- |------ |----------:|----------:|----------:|------:|
| Standard dictionary | 10 | 24.995 ns | 0.1821 ns | 0.1704 ns | 1.00 |
| Compiled dictionary | 10 | 9.366 ns | 0.0511 ns | 0.0478 ns | 0.37 |
| | | | | | |
| Standard dictionary | 1000 | 25.105 ns | 0.0665 ns | 0.0622 ns | 1.00 |
| Compiled dictionary | 1000 | 14.819 ns | 0.1138 ns | 0.1065 ns | 0.59 |
| | | | | | |
| Standard dictionary | 10000 | 29.047 ns | 0.1201 ns | 0.1123 ns | 1.00 |
| Compiled dictionary | 10000 | 17.903 ns | 0.0635 ns | 0.0530 ns | 0.62 |
Parsing 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
Inferring expression trees
from code
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.");
// //.
}
// Evalute 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
Providing context to 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

What's hot

Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev FedorProgramming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev FedorFedor Lavrentyev
 
Programming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
Programming Java - Lection 07 - Puzzlers - Lavrentyev FedorProgramming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
Programming Java - Lection 07 - Puzzlers - Lavrentyev FedorFedor Lavrentyev
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with GroovyArturo Herrero
 
Ast transformations
Ast transformationsAst transformations
Ast transformationsHamletDRC
 
TDC2016SP - Trilha .NET
TDC2016SP - Trilha .NETTDC2016SP - Trilha .NET
TDC2016SP - Trilha .NETtdc-globalcode
 
SWP - A Generic Language Parser
SWP - A Generic Language ParserSWP - A Generic Language Parser
SWP - A Generic Language Parserkamaelian
 
Spock: A Highly Logical Way To Test
Spock: A Highly Logical Way To TestSpock: A Highly Logical Way To Test
Spock: A Highly Logical Way To TestHoward Lewis Ship
 
Java 8 Streams & Collectors : the Leuven edition
Java 8 Streams & Collectors : the Leuven editionJava 8 Streams & Collectors : the Leuven edition
Java 8 Streams & Collectors : the Leuven editionJosé Paumard
 
Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1José Paumard
 
Tuga it 2016 - What's New In C# 6
Tuga it 2016 - What's New In C# 6Tuga it 2016 - What's New In C# 6
Tuga it 2016 - What's New In C# 6Paulo Morgado
 
The Ring programming language version 1.8 book - Part 31 of 202
The Ring programming language version 1.8 book - Part 31 of 202The Ring programming language version 1.8 book - Part 31 of 202
The Ring programming language version 1.8 book - Part 31 of 202Mahmoud Samir Fayed
 
The Ring programming language version 1.7 book - Part 35 of 196
The Ring programming language version 1.7 book - Part 35 of 196The Ring programming language version 1.7 book - Part 35 of 196
The Ring programming language version 1.7 book - Part 35 of 196Mahmoud Samir Fayed
 
AST Transformations
AST TransformationsAST Transformations
AST TransformationsHamletDRC
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to SwiftGiordano Scalzo
 
Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008Guillaume Laforge
 

What's hot (20)

Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev FedorProgramming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
Programming Java - Lection 04 - Generics and Lambdas - Lavrentyev Fedor
 
Programming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
Programming Java - Lection 07 - Puzzlers - Lavrentyev FedorProgramming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
Programming Java - Lection 07 - Puzzlers - Lavrentyev Fedor
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
 
Functional programming in java
Functional programming in javaFunctional programming in java
Functional programming in java
 
C# 7
C# 7C# 7
C# 7
 
What's New In C# 7
What's New In C# 7What's New In C# 7
What's New In C# 7
 
Ast transformations
Ast transformationsAst transformations
Ast transformations
 
TDC2016SP - Trilha .NET
TDC2016SP - Trilha .NETTDC2016SP - Trilha .NET
TDC2016SP - Trilha .NET
 
SWP - A Generic Language Parser
SWP - A Generic Language ParserSWP - A Generic Language Parser
SWP - A Generic Language Parser
 
Spock: A Highly Logical Way To Test
Spock: A Highly Logical Way To TestSpock: A Highly Logical Way To Test
Spock: A Highly Logical Way To Test
 
Java 8 Streams & Collectors : the Leuven edition
Java 8 Streams & Collectors : the Leuven editionJava 8 Streams & Collectors : the Leuven edition
Java 8 Streams & Collectors : the Leuven edition
 
Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1
 
Tuga it 2016 - What's New In C# 6
Tuga it 2016 - What's New In C# 6Tuga it 2016 - What's New In C# 6
Tuga it 2016 - What's New In C# 6
 
Scala 2 + 2 > 4
Scala 2 + 2 > 4Scala 2 + 2 > 4
Scala 2 + 2 > 4
 
Ggug spock
Ggug spockGgug spock
Ggug spock
 
The Ring programming language version 1.8 book - Part 31 of 202
The Ring programming language version 1.8 book - Part 31 of 202The Ring programming language version 1.8 book - Part 31 of 202
The Ring programming language version 1.8 book - Part 31 of 202
 
The Ring programming language version 1.7 book - Part 35 of 196
The Ring programming language version 1.7 book - Part 35 of 196The Ring programming language version 1.7 book - Part 35 of 196
The Ring programming language version 1.7 book - Part 35 of 196
 
AST Transformations
AST TransformationsAST Transformations
AST Transformations
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to Swift
 
Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008
 

Similar to Expression trees in c#

TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript IntroductionHans Höchtl
 
Introducing PHP Latest Updates
Introducing PHP Latest UpdatesIntroducing PHP Latest Updates
Introducing PHP Latest UpdatesIftekhar Eather
 
AST Transformations at JFokus
AST Transformations at JFokusAST Transformations at JFokus
AST Transformations at JFokusHamletDRC
 
Domain-Specific Languages
Domain-Specific LanguagesDomain-Specific Languages
Domain-Specific LanguagesJavier Canovas
 
Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)HamletDRC
 
What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)Pavlo Baron
 
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)James Titcumb
 
Getting started with Elasticsearch and .NET
Getting started with Elasticsearch and .NETGetting started with Elasticsearch and .NET
Getting started with Elasticsearch and .NETTomas Jansson
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeDaniel Wellman
 
C# 6 and 7 and Futures 20180607
C# 6 and 7 and Futures 20180607C# 6 and 7 and Futures 20180607
C# 6 and 7 and Futures 20180607Kevin Hazzard
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with ClojureDmitry Buzdin
 
tictactoe groovy
tictactoe groovytictactoe groovy
tictactoe groovyPaul King
 
Clojure for Java developers - Stockholm
Clojure for Java developers - StockholmClojure for Java developers - Stockholm
Clojure for Java developers - StockholmJan Kronquist
 
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...James Titcumb
 
Tuga IT 2017 - What's new in C# 7
Tuga IT 2017 - What's new in C# 7Tuga IT 2017 - What's new in C# 7
Tuga IT 2017 - What's new in C# 7Paulo Morgado
 
Domänenspezifische Sprachen mit Xtext
Domänenspezifische Sprachen mit XtextDomänenspezifische Sprachen mit Xtext
Domänenspezifische Sprachen mit XtextDr. Jan Köhnlein
 
Swift, functional programming, and the future of Objective-C
Swift, functional programming, and the future of Objective-CSwift, functional programming, and the future of Objective-C
Swift, functional programming, and the future of Objective-CAlexis Gallagher
 

Similar to Expression trees in c# (20)

TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
 
Introducing PHP Latest Updates
Introducing PHP Latest UpdatesIntroducing PHP Latest Updates
Introducing PHP Latest Updates
 
AST Transformations at JFokus
AST Transformations at JFokusAST Transformations at JFokus
AST Transformations at JFokus
 
Domain-Specific Languages
Domain-Specific LanguagesDomain-Specific Languages
Domain-Specific Languages
 
Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)
 
PHP pod mikroskopom
PHP pod mikroskopomPHP pod mikroskopom
PHP pod mikroskopom
 
What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)
 
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
 
Getting started with Elasticsearch and .NET
Getting started with Elasticsearch and .NETGetting started with Elasticsearch and .NET
Getting started with Elasticsearch and .NET
 
How to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy CodeHow to Start Test-Driven Development in Legacy Code
How to Start Test-Driven Development in Legacy Code
 
Java gets a closure
Java gets a closureJava gets a closure
Java gets a closure
 
C# 6 and 7 and Futures 20180607
C# 6 and 7 and Futures 20180607C# 6 and 7 and Futures 20180607
C# 6 and 7 and Futures 20180607
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
 
tictactoe groovy
tictactoe groovytictactoe groovy
tictactoe groovy
 
Clojure for Java developers - Stockholm
Clojure for Java developers - StockholmClojure for Java developers - Stockholm
Clojure for Java developers - Stockholm
 
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP...
 
Tuga IT 2017 - What's new in C# 7
Tuga IT 2017 - What's new in C# 7Tuga IT 2017 - What's new in C# 7
Tuga IT 2017 - What's new in C# 7
 
Java Puzzlers
Java PuzzlersJava Puzzlers
Java Puzzlers
 
Domänenspezifische Sprachen mit Xtext
Domänenspezifische Sprachen mit XtextDomänenspezifische Sprachen mit Xtext
Domänenspezifische Sprachen mit Xtext
 
Swift, functional programming, and the future of Objective-C
Swift, functional programming, and the future of Objective-CSwift, functional programming, and the future of Objective-C
Swift, functional programming, and the future of Objective-C
 

More from Oleksii Holub

Reality-Driven Testing using TestContainers
Reality-Driven Testing using TestContainersReality-Driven Testing using TestContainers
Reality-Driven Testing using TestContainersOleksii Holub
 
Fallacies of unit testing
Fallacies of unit testingFallacies of unit testing
Fallacies of unit testingOleksii Holub
 
GitHub Actions in action
GitHub Actions in actionGitHub Actions in action
GitHub Actions in actionOleksii Holub
 
Alexey Golub - Writing parsers in c# | 3Shape Meetup
Alexey Golub - Writing parsers in c# | 3Shape MeetupAlexey Golub - Writing parsers in c# | 3Shape Meetup
Alexey Golub - Writing parsers in c# | 3Shape MeetupOleksii Holub
 
Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...
Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...
Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...Oleksii Holub
 

More from Oleksii Holub (7)

Reality-Driven Testing using TestContainers
Reality-Driven Testing using TestContainersReality-Driven Testing using TestContainers
Reality-Driven Testing using TestContainers
 
Intro to CliWrap
Intro to CliWrapIntro to CliWrap
Intro to CliWrap
 
Intro to CliWrap
Intro to CliWrapIntro to CliWrap
Intro to CliWrap
 
Fallacies of unit testing
Fallacies of unit testingFallacies of unit testing
Fallacies of unit testing
 
GitHub Actions in action
GitHub Actions in actionGitHub Actions in action
GitHub Actions in action
 
Alexey Golub - Writing parsers in c# | 3Shape Meetup
Alexey Golub - Writing parsers in c# | 3Shape MeetupAlexey Golub - Writing parsers in c# | 3Shape Meetup
Alexey Golub - Writing parsers in c# | 3Shape Meetup
 
Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...
Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...
Alexey Golub - Dependency absolution (application as a pipeline) | Svitla Sma...
 

Recently uploaded

SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfPrecisely
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 

Recently uploaded (20)

SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 

Expression trees in c#

  • 1. 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
  • 2. /whois ${speaker} Speaker: Alexey Golub @Tyrrrz • Open-source developer ✨ • Conference speaker & blogger 🌐️ • C#, F#, JavaScript 💻 • Cloud & web ☁️ • Automation & DevOps ⚙️
  • 3. What is an expression tree? Speaker: Alexey Golub @Tyrrrz
  • 4. Speaker: Alexey Golub @Tyrrrz +Constant (2) Constant (3) Plus operator 2 3 Binary expression
  • 5. Speaker: Alexey Golub @Tyrrrz !string.IsNullOrWhiteSpace(personName) ? "Greetings, " + personName : null; string? GetGreeting(string personName) { return }
  • 6. Speaker: Alexey Golub @Tyrrrz "Greetings, " ! personName string.IsNullOrWhiteSpace( ) null;: ? + personName OPERATOR "NOT" METHOD CALL PARAMETER PARAMETERCONSTANT OPERATOR "ADD" CONSTANT
  • 7. Speaker: Alexey Golub @Tyrrrz { Ternary conditional } { + } TRUE { null } FALSE { Method call } CONDITION { string.IsNullOrWhiteSpace } { personName } { personName } { "Greetings, " } { ! }
  • 8. Speaker: Alexey Golub @Tyrrrz Expression Tree describes the structure of an expression
  • 11. 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 ...
  • 12. Speaker: Alexey Golub @Tyrrrz !string.IsNullOrWhiteSpace(personName) ? "Greetings, " + personName : null; Let’s recreate our expression dynamically
  • 13. 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(); }
  • 14. 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'.
  • 15. 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);
  • 16. Speaker: Alexey Golub @Tyrrrz var getGreetings = ConstructGreetingFunction(); var greetingsForJohn = getGreetings("John"); var greetingsForNobody = getGreetings(" "); // "Greetings, John" // <null>
  • 17. Not everything is an expression Speaker: Alexey Golub @Tyrrrz but we are not limited by that
  • 18. Speaker: Alexey Golub @Tyrrrz new StringBuilder() .Append("Hello ") .AppendLine("world!"); Statements Expression Console.Write("Hello "); Console.WriteLine("world!");
  • 19. Speaker: Alexey Golub @Tyrrrz public Expression CreateStatementBlock() { var consoleWriteMethod = typeof(Console) .GetMethod(nameof(Console.Write), new[] {typeof(string)}); var consoleWriteLineMethod = typeof(Console) .GetMethod(nameof(Console.WriteLine), new[] {typeof(string)}); return Expression.Block( Expression.Call(consoleWriteMethod, Expression.Constant("Hello ")), Expression.Call(consoleWriteLineMethod, Expression.Constant("world!"))); } var block = CreateStatementBlock(); var lambda = Expression.Lambda<Action>(block).Compile(); lambda(); // Hello world!
  • 20. Speaker: Alexey Golub @Tyrrrz public Expression CreateStatementBlock() { var variableA = Expression.Variable(typeof(string), "a"); var variableB = Expression.Variable(typeof(string), "b"); return Expression.Block( new[] {variableA, variableB}, Expression.Assign(variableA, Expression.Constant("Foo ")), Expression.Assign(variableB, Expression.Constant("bar")), Expression.Call(consoleWriteMethod, variableA), Expression.Call(consoleWriteLineMethod, variableB)); } Declare variables Assign values to variables Reference variables
  • 22. 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", Binding/lags.NonPublic | Binding/lags.Instance) .Invoke(command, null);
  • 23. Speaker: Alexey Golub @Tyrrrz public static class Re/lectionCached { private static MethodInfo ExecuteMethod { get; } = typeof(Command) .GetMethod("Execute", Binding/lags.NonPublic | Binding/lags.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
  • 24. 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
  • 25. 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 |
  • 27. 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
  • 28. 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
  • 29. Speaker: Alexey Golub @Tyrrrz public dynamic ThreeFourths(dynamic x) /> 3 * x / 4; Wait, how is it different from this?
  • 30. 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
  • 31. 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 |
  • 33. Speaker: Alexey Golub @Tyrrrz public TValue Lookup(TKey key) => key.GetHashCode() switch { // No collisions 9254 => value1, -101 => value2, // Collision 777 => key switch { key3 => value3, key4 => value4 }, // ... // Not found _ => throw new KeyNotFoundException(key.ToString()) }; Dictionary lookup in a nutshell
  • 34. Speaker: Alexey Golub @Tyrrrz public class CompiledDictionary<TKey, TValue> : IDictionary<TKey, TValue> { private readonly IDictionary<TKey, TValue> _inner = new Dictionary<TKey, TValue>(); private Func<TKey, TValue> _lookup; public void UpdateLookup() { // //. } public TValue this[TKey key] { get /> _lookup(key); set /> _inner[key] = value; } // The rest of the interface implementation is omitted for brevity }
  • 35. Speaker: Alexey Golub @Tyrrrz public void UpdateLookup() { var keyParameter = Expression.Parameter(typeof(TKey)); var keyGetHashCodeCall = Expression.Call( keyParameter, typeof(object).GetMethod(nameof(GetHashCode))); var keyToStringCall = Expression.Call( keyParameter, typeof(object).GetMethod(nameof(ToString))); var exceptionCtor = typeof(KeyNotFoundException) .GetConstructor(new[] {typeof(string)}); var throwException = Expression.Throw( Expression.New(exceptionCtor, keyToStringCall), typeof(TValue)); var body = Expression.Switch( // ... )); var lambda = Expression.Lambda<Func<TKey, TValue>>(body, keyParameter); _lookup = lambda.Compile(); }
  • 36. Speaker: Alexey Golub @Tyrrrz var body = Expression.Switch( typeof(TValue), keyGetHashCodeCall, throwException, null, _inner .GroupBy(p /> p.Key.GetHashCode()) .Select(g /> { if (g.Count() /= 1) return Expression.SwitchCase( Expression.Constant(g.Single().Value), Expression.Constant(g.Key)); return Expression.SwitchCase( Expression.Switch( typeof(TValue), keyParameter, throwException, null, g.Select(p /> Expression.SwitchCase( Expression.Constant(p.Value), Expression.Constant(p.Key) ))), Expression.Constant(g.Key)); })); No collision Collision
  • 37. Speaker: Alexey Golub @Tyrrrz | Method | Count | Mean | Error | StdDev | Ratio | |-------------------- |------ |----------:|----------:|----------:|------:| | Standard dictionary | 10 | 24.995 ns | 0.1821 ns | 0.1704 ns | 1.00 | | Compiled dictionary | 10 | 9.366 ns | 0.0511 ns | 0.0478 ns | 0.37 | | | | | | | | | Standard dictionary | 1000 | 25.105 ns | 0.0665 ns | 0.0622 ns | 1.00 | | Compiled dictionary | 1000 | 14.819 ns | 0.1138 ns | 0.1065 ns | 0.59 | | | | | | | | | Standard dictionary | 10000 | 29.047 ns | 0.1201 ns | 0.1123 ns | 1.00 | | Compiled dictionary | 10000 | 17.903 ns | 0.0635 ns | 0.0530 ns | 0.62 |
  • 38. Parsing into expression trees Speaker: Alexey Golub @Tyrrrz
  • 39. 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(); } }
  • 40. 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
  • 41. Inferring expression trees from code Speaker: Alexey Golub @Tyrrrz
  • 42. 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]
  • 43. 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)
  • 44. 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
  • 45. 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
  • 46. Speaker: Alexey Golub @Tyrrrz Limitations Func<int, int, int> div = (a, b) /> a / b; Expression<Func<int, int, int/> divExpr = div; Compilation error
  • 47. 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
  • 48. 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:
  • 49. Identifying type members Speaker: Alexey Golub @Tyrrrz
  • 50. 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)
  • 51. 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."); // //. } // Evalute 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
  • 52. 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
  • 53. 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
  • 54. Providing context to assertions Speaker: Alexey Golub @Tyrrrz
  • 55. 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
  • 56. 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
  • 57. 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))); }
  • 58. Traversing and rewriting expression trees Speaker: Alexey Golub @Tyrrrz
  • 60. 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); } }
  • 61. 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()
  • 62. 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); } }
  • 63. 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
  • 64. Transpiling code into a different language Speaker: Alexey Golub @Tyrrrz
  • 65. Speaker: Alexey Golub @Tyrrrz Expression<Action<int, int/> expr = (a, b) /> Console.WriteLine("a + b = {0}", a + b)); var fsharpCode = FSharpTranspiler.Convert(expr);
  • 66. 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(); } }
  • 67. 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); }
  • 68. 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)
  • 69. 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
  • 70. 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
  • 71. 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
  • 72. Thank you! Speaker: Alexey Golub @Tyrrrz