Skip to content

Instantly share code, notes, and snippets.

@erinnmclaughlin
Created June 7, 2025 13:29
Show Gist options
  • Select an option

  • Save erinnmclaughlin/aa993f3f25541822ba33ac61b0f6060d to your computer and use it in GitHub Desktop.

Select an option

Save erinnmclaughlin/aa993f3f25541822ba33ac61b0f6060d to your computer and use it in GitHub Desktop.
C# Expression Trees
using System.Linq.Expressions;
// An expression simply turns a delegate into a data about itself. So a => a + 1 becomes something like "On the left side there's an int a. On the right side you add 1 to it."
// Func doesn't carry with it a way to get into itself.
// Source: https://stackoverflow.com/a/34606818
// This is a function that will add 1 to the input a.
// Inside your code, this does exactly that.
// However, let's say you want to translate this function into SQL. Your code will need to know what this fuction DOES.
// This is not possibly with just a Func. So...
Func<int, int> addOne = (int a) => a + 1;
// Enter the EXPRESSION. The expression is data that describes how the function WORKS.
// In practice, you write it essentially the same way you write a function, but what actually happens is that this becomes a data structure that can be analyzed. It also can be compiled down into the actual function and executed.
Expression<Func<int, int>> addOneExpression = (int a) => a + 1;
Expression<Func<int, int, int>> sumExpression = (int a, int b) => a + b;
Expression<Func<int, int, bool>> equalityExpression = (int a, int b) => a == b;
Expression<Func<int, int, int>> subtractBFromAExpression = (int a, int b) => a - b;
Expression<Func<int, int, int>> subtractAFromBExpression = (int a, int b) => b - a;
Expression<Func<int, int, int>> multiplyThenAddExpression = (int a, int b) => (a * 2) + b;
// note: this not valid:
// Expression<Func<int, int>> addOneExpression = addOne;
// because you can't convert a Func into an Expression. The Func is just a delegate, while the Expression is a data structure that describes the function. You have to start with the description.
// with expressions, I can do this:
Describe(addOneExpression);
Describe(sumExpression);
Describe(equalityExpression);
Describe(subtractBFromAExpression);
Describe(subtractAFromBExpression);
Describe(multiplyThenAddExpression);
// You can do SOME of this using reflection, but expressions are much more powerful, ESPECIALLY when it comes to evaluating what the function is doing.
// Note that you cannot write an expression like this:
// int mutableNumber = 1;
// Expression<Func<int>> mutableNumberExpression = () => mutableNumber++;
// because the expression must be deterministic. The expression must always return the same result for the same input. If you have a mutable variable, it can change between calls, so the expression is not deterministic.
static void Describe<T>(Expression<T> expression)
{
Console.WriteLine($"The expression \"{expression}\" takes {expression.Parameters.Count} parameter{(expression.Parameters.Count == 1 ? "" : "s")}:");
foreach (var parameter in expression.Parameters)
{
Console.WriteLine($" - {parameter.Name} (Type {parameter.Type})");
}
if (expression.Body is BinaryExpression binaryExpression)
{
var (left, right) = (binaryExpression.Left, binaryExpression.Right);
Console.WriteLine("The expressed function" + binaryExpression.NodeType switch
{
ExpressionType.Add => $" adds {right} to {left}.",
ExpressionType.Subtract => $" subtracts {right} from {left}.",
ExpressionType.Multiply => $" multiplies {left} by {right}.",
ExpressionType.Divide => $" divides {left} by {right}.",
ExpressionType.Modulo => $" calculates the remainder of {left} divided by {right}.",
ExpressionType.Equal => $" checks if {left} is equal to {right}.",
ExpressionType.NotEqual => $" checks if {left} is not equal to {right}.",
ExpressionType.GreaterThan => $" checks if {left} is greater than {right}.",
ExpressionType.GreaterThanOrEqual => $" checks if {left} is greater than or equal to {right}.",
ExpressionType.LessThan => $" checks if {left} is less than {right}.",
ExpressionType.LessThanOrEqual => $" checks if {left} is less than or equal to {right}.",
_ => $" performs an unknown operation on {left} and {right} (NodeType: {binaryExpression.NodeType})."
});
}
else
{
Console.WriteLine("The expression is not a binary expression.");
}
Console.WriteLine($"It returns a result of type {expression.ReturnType}.");
Console.WriteLine();
}
/*
Output:
The expression "a => (a + 1)" takes 1 parameter:
- a (Type System.Int32)
The expressed function adds 1 to a.
It returns a result of type System.Int32.
The expression "(a, b) => (a + b)" takes 2 parameters:
- a (Type System.Int32)
- b (Type System.Int32)
The expressed function adds b to a.
It returns a result of type System.Int32.
The expression "(a, b) => (a == b)" takes 2 parameters:
- a (Type System.Int32)
- b (Type System.Int32)
The expressed function checks if a is equal to b.
It returns a result of type System.Boolean.
The expression "(a, b) => (a - b)" takes 2 parameters:
- a (Type System.Int32)
- b (Type System.Int32)
The expressed function subtracts b from a.
It returns a result of type System.Int32.
The expression "(a, b) => (b - a)" takes 2 parameters:
- a (Type System.Int32)
- b (Type System.Int32)
The expressed function subtracts a from b.
It returns a result of type System.Int32.
The expression "(a, b) => ((a * 2) + b)" takes 2 parameters:
- a (Type System.Int32)
- b (Type System.Int32)
The expressed function adds b to (a * 2).
It returns a result of type System.Int32.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment