SlideShare a Scribd company logo
1 of 72
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 'S
ystem.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", 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 ReflectionDelegate
{
private static MethodInfo ExecuteMethod { get; } = typeof(Command)
.GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.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", BindingFlags.NonPublic | BindingFlags.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

SeaJUG March 2004 - Groovy
SeaJUG March 2004 - GroovySeaJUG March 2004 - Groovy
SeaJUG March 2004 - Groovy
Ted Leung
 
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
José Paumard
 

What's hot (20)

Functional programming in java
Functional programming in javaFunctional programming in java
Functional programming in java
 
Functional Programming In Java
Functional Programming In JavaFunctional Programming In Java
Functional Programming In Java
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
 
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
 
Ast transformations
Ast transformationsAst transformations
Ast transformations
 
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
 
つくってあそぼ Kotlin DSL ~拡張編~
つくってあそぼ Kotlin DSL ~拡張編~つくってあそぼ Kotlin DSL ~拡張編~
つくってあそぼ Kotlin DSL ~拡張編~
 
SeaJUG March 2004 - Groovy
SeaJUG March 2004 - GroovySeaJUG March 2004 - Groovy
SeaJUG March 2004 - Groovy
 
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
 
Scala 2 + 2 > 4
Scala 2 + 2 > 4Scala 2 + 2 > 4
Scala 2 + 2 > 4
 
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
 
つくってあそぼ Kotlin DSL 第2版
つくってあそぼ Kotlin DSL 第2版つくってあそぼ Kotlin DSL 第2版
つくってあそぼ Kotlin DSL 第2版
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to Swift
 
Java Class Design
Java Class DesignJava Class Design
Java Class Design
 
DRYing to Monad in Java8
DRYing to Monad in Java8DRYing to Monad in Java8
DRYing to Monad in Java8
 
Currying and Partial Function Application (PFA)
Currying and Partial Function Application (PFA)Currying and Partial Function Application (PFA)
Currying and Partial Function Application (PFA)
 
Ggug spock
Ggug spockGgug spock
Ggug spock
 
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#, Алексей Голубь (Svitla Systems)

Introducing PHP Latest Updates
Introducing PHP Latest UpdatesIntroducing PHP Latest Updates
Introducing PHP Latest Updates
Iftekhar Eather
 
AST Transformations at JFokus
AST Transformations at JFokusAST Transformations at JFokus
AST Transformations at JFokus
HamletDRC
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
Dmitry Buzdin
 
AST Transformations
AST TransformationsAST Transformations
AST Transformations
HamletDRC
 
Clojure for Java developers - Stockholm
Clojure for Java developers - StockholmClojure for Java developers - Stockholm
Clojure for Java developers - Stockholm
Jan Kronquist
 

Similar to Expression trees in c#, Алексей Голубь (Svitla Systems) (20)

TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
 
Introducing PHP Latest Updates
Introducing PHP Latest UpdatesIntroducing PHP Latest Updates
Introducing PHP Latest Updates
 
Domain-Specific Languages
Domain-Specific LanguagesDomain-Specific Languages
Domain-Specific Languages
 
AST Transformations at JFokus
AST Transformations at JFokusAST Transformations at JFokus
AST Transformations at JFokus
 
Java gets a closure
Java gets a closureJava gets a closure
Java gets a closure
 
Java Puzzlers
Java PuzzlersJava Puzzlers
Java Puzzlers
 
PHP pod mikroskopom
PHP pod mikroskopomPHP pod mikroskopom
PHP pod mikroskopom
 
Getting started with Elasticsearch and .NET
Getting started with Elasticsearch and .NETGetting started with Elasticsearch and .NET
Getting started with Elasticsearch and .NET
 
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)
 
Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)
 
tictactoe groovy
tictactoe groovytictactoe groovy
tictactoe groovy
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
 
AST Transformations
AST TransformationsAST Transformations
AST Transformations
 
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
 
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)
 
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
 
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
 
What's New In C# 7
What's New In C# 7What's New In C# 7
What's New In C# 7
 
Introduction to Functional Programming
Introduction to Functional ProgrammingIntroduction to Functional Programming
Introduction to Functional Programming
 
Clojure for Java developers - Stockholm
Clojure for Java developers - StockholmClojure for Java developers - Stockholm
Clojure for Java developers - Stockholm
 

More from Alina Vilk

Architecture components, Константин Марс, TeamLead, Senior Developer, DataArt
Architecture components, Константин Марс, TeamLead, Senior Developer, DataArtArchitecture components, Константин Марс, TeamLead, Senior Developer, DataArt
Architecture components, Константин Марс, TeamLead, Senior Developer, DataArt
Alina Vilk
 
Al around ML 2017, Оксана Савенко, студентка НТУ имени Каразина
Al around ML 2017, Оксана Савенко, студентка НТУ имени КаразинаAl around ML 2017, Оксана Савенко, студентка НТУ имени Каразина
Al around ML 2017, Оксана Савенко, студентка НТУ имени Каразина
Alina Vilk
 
Кирилл Безпалый, .NET Developer, Ciklum
Кирилл Безпалый, .NET Developer, CiklumКирилл Безпалый, .NET Developer, Ciklum
Кирилл Безпалый, .NET Developer, Ciklum
Alina Vilk
 
Александр Сергиенко, Senior Android Developer, DataArt
Александр Сергиенко, Senior Android Developer, DataArtАлександр Сергиенко, Senior Android Developer, DataArt
Александр Сергиенко, Senior Android Developer, DataArt
Alina Vilk
 

More from Alina Vilk (20)

Designer in you, Irina Shapoval, Lead Designer, DataArt
Designer in you, Irina Shapoval, Lead Designer, DataArtDesigner in you, Irina Shapoval, Lead Designer, DataArt
Designer in you, Irina Shapoval, Lead Designer, DataArt
 
.NET framework vs .net core 3.1 commons &amp; differences
 .NET framework vs .net core 3.1  commons &amp; differences .NET framework vs .net core 3.1  commons &amp; differences
.NET framework vs .net core 3.1 commons &amp; differences
 
"Dev to PM" D.Fedotov
"Dev to PM" D.Fedotov"Dev to PM" D.Fedotov
"Dev to PM" D.Fedotov
 
Игорь Литвиненко (Senior iOS- и Android-разработчик,DataArt)
Игорь Литвиненко (Senior iOS- и Android-разработчик,DataArt)Игорь Литвиненко (Senior iOS- и Android-разработчик,DataArt)
Игорь Литвиненко (Senior iOS- и Android-разработчик,DataArt)
 
Алексей Рыбаков (Senior Engineer,Technical Evangelist DataArt )
Алексей Рыбаков (Senior Engineer,Technical Evangelist DataArt ) Алексей Рыбаков (Senior Engineer,Technical Evangelist DataArt )
Алексей Рыбаков (Senior Engineer,Technical Evangelist DataArt )
 
Ирина Шаповал,(Lead UI/UX дизайнер, DataArt)
Ирина Шаповал,(Lead UI/UX дизайнер, DataArt)Ирина Шаповал,(Lead UI/UX дизайнер, DataArt)
Ирина Шаповал,(Lead UI/UX дизайнер, DataArt)
 
Devops, v.02, Alexander Pavlenko (DataArt)
Devops, v.02, Alexander Pavlenko (DataArt)Devops, v.02, Alexander Pavlenko (DataArt)
Devops, v.02, Alexander Pavlenko (DataArt)
 
O DevOps, Stanislav Kolenkin ( DataArt)
O DevOps, Stanislav Kolenkin ( DataArt)O DevOps, Stanislav Kolenkin ( DataArt)
O DevOps, Stanislav Kolenkin ( DataArt)
 
Interactive 3D graphics for web with three.js, Andrey Vedilin, DataArt
Interactive  3D graphics for web with three.js, Andrey Vedilin, DataArtInteractive  3D graphics for web with three.js, Andrey Vedilin, DataArt
Interactive 3D graphics for web with three.js, Andrey Vedilin, DataArt
 
Architecture components, Константин Марс, TeamLead, Senior Developer, DataArt
Architecture components, Константин Марс, TeamLead, Senior Developer, DataArtArchitecture components, Константин Марс, TeamLead, Senior Developer, DataArt
Architecture components, Константин Марс, TeamLead, Senior Developer, DataArt
 
Al around ML 2017, Оксана Савенко, студентка НТУ имени Каразина
Al around ML 2017, Оксана Савенко, студентка НТУ имени КаразинаAl around ML 2017, Оксана Савенко, студентка НТУ имени Каразина
Al around ML 2017, Оксана Савенко, студентка НТУ имени Каразина
 
Встреча Google Post IO ( Владимир Иванов, Катерина Заворотченко и Сергей Комлач)
Встреча Google Post IO ( Владимир Иванов, Катерина Заворотченко и Сергей Комлач)Встреча Google Post IO ( Владимир Иванов, Катерина Заворотченко и Сергей Комлач)
Встреча Google Post IO ( Владимир Иванов, Катерина Заворотченко и Сергей Комлач)
 
Кирилл Безпалый, .NET Developer, Ciklum
Кирилл Безпалый, .NET Developer, CiklumКирилл Безпалый, .NET Developer, Ciklum
Кирилл Безпалый, .NET Developer, Ciklum
 
Игорь Леонтьев, Lead Architect on all Blockchain projects of Viseo group
Игорь Леонтьев, Lead Architect on all Blockchain projects of Viseo groupИгорь Леонтьев, Lead Architect on all Blockchain projects of Viseo group
Игорь Леонтьев, Lead Architect on all Blockchain projects of Viseo group
 
Александр Сергиенко, Senior Android Developer, DataArt
Александр Сергиенко, Senior Android Developer, DataArtАлександр Сергиенко, Senior Android Developer, DataArt
Александр Сергиенко, Senior Android Developer, DataArt
 
Евгений Дубовик, Senior Developer, DataArtDb presentation gdg
Евгений Дубовик, Senior Developer, DataArtDb presentation gdgЕвгений Дубовик, Senior Developer, DataArtDb presentation gdg
Евгений Дубовик, Senior Developer, DataArtDb presentation gdg
 
Дмитрий Козицкий,Lead UX / UI Designer, DataArt
Дмитрий Козицкий,Lead UX / UI Designer, DataArtДмитрий Козицкий,Lead UX / UI Designer, DataArt
Дмитрий Козицкий,Lead UX / UI Designer, DataArt
 
Игорь Юзовицкий,UX Expert, DataArt
Игорь Юзовицкий,UX Expert, DataArtИгорь Юзовицкий,UX Expert, DataArt
Игорь Юзовицкий,UX Expert, DataArt
 
Android Things, Alexey Rybakov, Technical Evangelist, DataArt
Android Things, Alexey Rybakov, Technical Evangelist, DataArtAndroid Things, Alexey Rybakov, Technical Evangelist, DataArt
Android Things, Alexey Rybakov, Technical Evangelist, DataArt
 
«Делегирование как идеальный способ угробить проект», Александр Ивахненко, IT...
«Делегирование как идеальный способ угробить проект», Александр Ивахненко, IT...«Делегирование как идеальный способ угробить проект», Александр Ивахненко, IT...
«Делегирование как идеальный способ угробить проект», Александр Ивахненко, IT...
 

Recently uploaded

Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in DelhiRussian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
kauryashika82
 
1029-Danh muc Sach Giao Khoa khoi 6.pdf
1029-Danh muc Sach Giao Khoa khoi  6.pdf1029-Danh muc Sach Giao Khoa khoi  6.pdf
1029-Danh muc Sach Giao Khoa khoi 6.pdf
QucHHunhnh
 
1029 - Danh muc Sach Giao Khoa 10 . pdf
1029 -  Danh muc Sach Giao Khoa 10 . pdf1029 -  Danh muc Sach Giao Khoa 10 . pdf
1029 - Danh muc Sach Giao Khoa 10 . pdf
QucHHunhnh
 
An Overview of Mutual Funds Bcom Project.pdf
An Overview of Mutual Funds Bcom Project.pdfAn Overview of Mutual Funds Bcom Project.pdf
An Overview of Mutual Funds Bcom Project.pdf
SanaAli374401
 
Making and Justifying Mathematical Decisions.pdf
Making and Justifying Mathematical Decisions.pdfMaking and Justifying Mathematical Decisions.pdf
Making and Justifying Mathematical Decisions.pdf
Chris Hunter
 
Gardella_Mateo_IntellectualProperty.pdf.
Gardella_Mateo_IntellectualProperty.pdf.Gardella_Mateo_IntellectualProperty.pdf.
Gardella_Mateo_IntellectualProperty.pdf.
MateoGardella
 

Recently uploaded (20)

Ecological Succession. ( ECOSYSTEM, B. Pharmacy, 1st Year, Sem-II, Environmen...
Ecological Succession. ( ECOSYSTEM, B. Pharmacy, 1st Year, Sem-II, Environmen...Ecological Succession. ( ECOSYSTEM, B. Pharmacy, 1st Year, Sem-II, Environmen...
Ecological Succession. ( ECOSYSTEM, B. Pharmacy, 1st Year, Sem-II, Environmen...
 
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in DelhiRussian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
Russian Escort Service in Delhi 11k Hotel Foreigner Russian Call Girls in Delhi
 
1029-Danh muc Sach Giao Khoa khoi 6.pdf
1029-Danh muc Sach Giao Khoa khoi  6.pdf1029-Danh muc Sach Giao Khoa khoi  6.pdf
1029-Danh muc Sach Giao Khoa khoi 6.pdf
 
SECOND SEMESTER TOPIC COVERAGE SY 2023-2024 Trends, Networks, and Critical Th...
SECOND SEMESTER TOPIC COVERAGE SY 2023-2024 Trends, Networks, and Critical Th...SECOND SEMESTER TOPIC COVERAGE SY 2023-2024 Trends, Networks, and Critical Th...
SECOND SEMESTER TOPIC COVERAGE SY 2023-2024 Trends, Networks, and Critical Th...
 
1029 - Danh muc Sach Giao Khoa 10 . pdf
1029 -  Danh muc Sach Giao Khoa 10 . pdf1029 -  Danh muc Sach Giao Khoa 10 . pdf
1029 - Danh muc Sach Giao Khoa 10 . pdf
 
Accessible design: Minimum effort, maximum impact
Accessible design: Minimum effort, maximum impactAccessible design: Minimum effort, maximum impact
Accessible design: Minimum effort, maximum impact
 
ICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptxICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptx
 
microwave assisted reaction. General introduction
microwave assisted reaction. General introductionmicrowave assisted reaction. General introduction
microwave assisted reaction. General introduction
 
Unit-IV- Pharma. Marketing Channels.pptx
Unit-IV- Pharma. Marketing Channels.pptxUnit-IV- Pharma. Marketing Channels.pptx
Unit-IV- Pharma. Marketing Channels.pptx
 
INDIA QUIZ 2024 RLAC DELHI UNIVERSITY.pptx
INDIA QUIZ 2024 RLAC DELHI UNIVERSITY.pptxINDIA QUIZ 2024 RLAC DELHI UNIVERSITY.pptx
INDIA QUIZ 2024 RLAC DELHI UNIVERSITY.pptx
 
Mixin Classes in Odoo 17 How to Extend Models Using Mixin Classes
Mixin Classes in Odoo 17  How to Extend Models Using Mixin ClassesMixin Classes in Odoo 17  How to Extend Models Using Mixin Classes
Mixin Classes in Odoo 17 How to Extend Models Using Mixin Classes
 
Sports & Fitness Value Added Course FY..
Sports & Fitness Value Added Course FY..Sports & Fitness Value Added Course FY..
Sports & Fitness Value Added Course FY..
 
This PowerPoint helps students to consider the concept of infinity.
This PowerPoint helps students to consider the concept of infinity.This PowerPoint helps students to consider the concept of infinity.
This PowerPoint helps students to consider the concept of infinity.
 
An Overview of Mutual Funds Bcom Project.pdf
An Overview of Mutual Funds Bcom Project.pdfAn Overview of Mutual Funds Bcom Project.pdf
An Overview of Mutual Funds Bcom Project.pdf
 
Making and Justifying Mathematical Decisions.pdf
Making and Justifying Mathematical Decisions.pdfMaking and Justifying Mathematical Decisions.pdf
Making and Justifying Mathematical Decisions.pdf
 
Mattingly "AI & Prompt Design: The Basics of Prompt Design"
Mattingly "AI & Prompt Design: The Basics of Prompt Design"Mattingly "AI & Prompt Design: The Basics of Prompt Design"
Mattingly "AI & Prompt Design: The Basics of Prompt Design"
 
Gardella_Mateo_IntellectualProperty.pdf.
Gardella_Mateo_IntellectualProperty.pdf.Gardella_Mateo_IntellectualProperty.pdf.
Gardella_Mateo_IntellectualProperty.pdf.
 
Unit-IV; Professional Sales Representative (PSR).pptx
Unit-IV; Professional Sales Representative (PSR).pptxUnit-IV; Professional Sales Representative (PSR).pptx
Unit-IV; Professional Sales Representative (PSR).pptx
 
PROCESS RECORDING FORMAT.docx
PROCESS      RECORDING        FORMAT.docxPROCESS      RECORDING        FORMAT.docx
PROCESS RECORDING FORMAT.docx
 
Grant Readiness 101 TechSoup and Remy Consulting
Grant Readiness 101 TechSoup and Remy ConsultingGrant Readiness 101 TechSoup and Remy Consulting
Grant Readiness 101 TechSoup and Remy Consulting
 

Expression trees in c#, Алексей Голубь (Svitla Systems)

  • 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 'S ystem.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", BindingFlags.NonPublic | BindingFlags.Instance) .Invoke(command, null);
  • 23. 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 ReflectionDelegate { private static MethodInfo ExecuteMethod { get; } = typeof(Command) .GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.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", BindingFlags.NonPublic | BindingFlags.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