SlideShare a Scribd company logo
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 Fedor
Fedor 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 Fedor
Fedor Lavrentyev
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
Arturo Herrero
 
Functional programming in java
Functional programming in javaFunctional programming in java
Functional programming in java
John Ferguson Smart Limited
 
C# 7
C# 7C# 7
What's New In C# 7
What's New In C# 7What's New In C# 7
What's New In C# 7
Paulo Morgado
 
Ast transformations
Ast transformationsAst transformations
Ast transformations
HamletDRC
 
TDC2016SP - Trilha .NET
TDC2016SP - Trilha .NETTDC2016SP - Trilha .NET
TDC2016SP - Trilha .NET
tdc-globalcode
 
SWP - A Generic Language Parser
SWP - A Generic Language ParserSWP - A Generic Language Parser
SWP - A Generic Language Parser
kamaelian
 
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
Howard 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 edition
José 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 1
José 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# 6
Paulo Morgado
 
Scala 2 + 2 > 4
Scala 2 + 2 > 4Scala 2 + 2 > 4
Scala 2 + 2 > 4
Emil Vladev
 
Ggug spock
Ggug spockGgug spock
Ggug spock
Skills Matter
 
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
Mahmoud 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 196
Mahmoud Samir Fayed
 
AST Transformations
AST TransformationsAST Transformations
AST Transformations
HamletDRC
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to Swift
Giordano Scalzo
 
Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008Groovy Introduction - JAX Germany - 2008
Groovy Introduction - JAX Germany - 2008
Guillaume 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 Introduction
Hans Höchtl
 
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
 
Domain-Specific Languages
Domain-Specific LanguagesDomain-Specific Languages
Domain-Specific Languages
Javier Canovas
 
Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)Groovy Ast Transformations (greach)
Groovy Ast Transformations (greach)
HamletDRC
 
PHP pod mikroskopom
PHP pod mikroskopomPHP pod mikroskopom
PHP pod mikroskopom
Saša Stamenković
 
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 .NET
Tomas 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 Code
Daniel Wellman
 
Java gets a closure
Java gets a closureJava gets a closure
Java gets a closure
Tomasz Kowalczewski
 
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
Kevin Hazzard
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
Dmitry Buzdin
 
tictactoe groovy
tictactoe groovytictactoe groovy
tictactoe groovy
Paul King
 
Clojure for Java developers - Stockholm
Clojure for Java developers - StockholmClojure for Java developers - Stockholm
Clojure for Java developers - Stockholm
Jan 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# 7
Paulo Morgado
 
Java Puzzlers
Java PuzzlersJava Puzzlers
Java Puzzlers
Dmitry Buzdin
 
Domänenspezifische Sprachen mit Xtext
Domänenspezifische Sprachen mit XtextDomänenspezifische Sprachen mit Xtext
Domänenspezifische Sprachen mit Xtext
Dr. 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-C
Alexis 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 TestContainers
Oleksii Holub
 
Intro to CliWrap
Intro to CliWrapIntro to CliWrap
Intro to CliWrap
Oleksii Holub
 
Intro to CliWrap
Intro to CliWrapIntro to CliWrap
Intro to CliWrap
Oleksii Holub
 
Fallacies of unit testing
Fallacies of unit testingFallacies of unit testing
Fallacies of unit testing
Oleksii Holub
 
GitHub Actions in action
GitHub Actions in actionGitHub Actions in action
GitHub Actions in action
Oleksii 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 Meetup
Oleksii 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

Christine's Supplier Sourcing Presentaion.pptx
Christine's Supplier Sourcing Presentaion.pptxChristine's Supplier Sourcing Presentaion.pptx
Christine's Supplier Sourcing Presentaion.pptx
christinelarrosa
 
Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |
AstuteBusiness
 
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
saastr
 
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
Edge AI and Vision Alliance
 
Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving | Nameplate Manufacturing Process - 2024Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving
 
Leveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and StandardsLeveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and Standards
Neo4j
 
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
Jason Yip
 
What is an RPA CoE? Session 1 – CoE Vision
What is an RPA CoE?  Session 1 – CoE VisionWhat is an RPA CoE?  Session 1 – CoE Vision
What is an RPA CoE? Session 1 – CoE Vision
DianaGray10
 
Christine's Product Research Presentation.pptx
Christine's Product Research Presentation.pptxChristine's Product Research Presentation.pptx
Christine's Product Research Presentation.pptx
christinelarrosa
 
Mutation Testing for Task-Oriented Chatbots
Mutation Testing for Task-Oriented ChatbotsMutation Testing for Task-Oriented Chatbots
Mutation Testing for Task-Oriented Chatbots
Pablo Gómez Abajo
 
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectorsConnector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
DianaGray10
 
Session 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdfSession 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdf
UiPathCommunity
 
Choosing The Best AWS Service For Your Website + API.pptx
Choosing The Best AWS Service For Your Website + API.pptxChoosing The Best AWS Service For Your Website + API.pptx
Choosing The Best AWS Service For Your Website + API.pptx
Brandon Minnick, MBA
 
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance PanelsNorthern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving
 
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge GraphGraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
Neo4j
 
PRODUCT LISTING OPTIMIZATION PRESENTATION.pptx
PRODUCT LISTING OPTIMIZATION PRESENTATION.pptxPRODUCT LISTING OPTIMIZATION PRESENTATION.pptx
PRODUCT LISTING OPTIMIZATION PRESENTATION.pptx
christinelarrosa
 
Your One-Stop Shop for Python Success: Top 10 US Python Development Providers
Your One-Stop Shop for Python Success: Top 10 US Python Development ProvidersYour One-Stop Shop for Python Success: Top 10 US Python Development Providers
Your One-Stop Shop for Python Success: Top 10 US Python Development Providers
akankshawande
 
AppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSFAppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSF
Ajin Abraham
 
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdfMonitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Tosin Akinosho
 
Demystifying Knowledge Management through Storytelling
Demystifying Knowledge Management through StorytellingDemystifying Knowledge Management through Storytelling
Demystifying Knowledge Management through Storytelling
Enterprise Knowledge
 

Recently uploaded (20)

Christine's Supplier Sourcing Presentaion.pptx
Christine's Supplier Sourcing Presentaion.pptxChristine's Supplier Sourcing Presentaion.pptx
Christine's Supplier Sourcing Presentaion.pptx
 
Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |
 
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
9 CEO's who hit $100m ARR Share Their Top Growth Tactics Nathan Latka, Founde...
 
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
“Temporal Event Neural Networks: A More Efficient Alternative to the Transfor...
 
Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving | Nameplate Manufacturing Process - 2024Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving | Nameplate Manufacturing Process - 2024
 
Leveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and StandardsLeveraging the Graph for Clinical Trials and Standards
Leveraging the Graph for Clinical Trials and Standards
 
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
 
What is an RPA CoE? Session 1 – CoE Vision
What is an RPA CoE?  Session 1 – CoE VisionWhat is an RPA CoE?  Session 1 – CoE Vision
What is an RPA CoE? Session 1 – CoE Vision
 
Christine's Product Research Presentation.pptx
Christine's Product Research Presentation.pptxChristine's Product Research Presentation.pptx
Christine's Product Research Presentation.pptx
 
Mutation Testing for Task-Oriented Chatbots
Mutation Testing for Task-Oriented ChatbotsMutation Testing for Task-Oriented Chatbots
Mutation Testing for Task-Oriented Chatbots
 
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectorsConnector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
 
Session 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdfSession 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdf
 
Choosing The Best AWS Service For Your Website + API.pptx
Choosing The Best AWS Service For Your Website + API.pptxChoosing The Best AWS Service For Your Website + API.pptx
Choosing The Best AWS Service For Your Website + API.pptx
 
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance PanelsNorthern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
 
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge GraphGraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
 
PRODUCT LISTING OPTIMIZATION PRESENTATION.pptx
PRODUCT LISTING OPTIMIZATION PRESENTATION.pptxPRODUCT LISTING OPTIMIZATION PRESENTATION.pptx
PRODUCT LISTING OPTIMIZATION PRESENTATION.pptx
 
Your One-Stop Shop for Python Success: Top 10 US Python Development Providers
Your One-Stop Shop for Python Success: Top 10 US Python Development ProvidersYour One-Stop Shop for Python Success: Top 10 US Python Development Providers
Your One-Stop Shop for Python Success: Top 10 US Python Development Providers
 
AppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSFAppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSF
 
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdfMonitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdf
 
Demystifying Knowledge Management through Storytelling
Demystifying Knowledge Management through StorytellingDemystifying Knowledge Management through Storytelling
Demystifying Knowledge Management through Storytelling
 

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