Created
June 7, 2025 13:29
-
-
Save erinnmclaughlin/aa993f3f25541822ba33ac61b0f6060d to your computer and use it in GitHub Desktop.
C# Expression Trees
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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