In this six-part series, we want to experiment with expression trees and create two Enumerable and Queryable extensions helping us search through a collection of objects using a string keyword and list of properties in which we want to search. In the first part of the article, we introduce the basic concepts needed for understanding the next parts of the article. This part lays a concrete foundation for understanding the next parts. To do so, it would be a good idea to briefly review the concepts of Delegates, lambdas, and expression trees. Delegates, lambdas, and expression trees are all features in C# that allow for more flexible and functional programming. Let's break down each concept and make an example for each of them.

Delegates

In C#, a delegate is a type that represents a reference to a method with a specific parameter list and return type. It essentially allows you to treat methods as first-class citizens, meaning you can pass them as parameters to other methods, store them in variables, and invoke them dynamically. Delegates are often used to implement event handling, callback mechanisms, and other scenarios where you need to encapsulate and invoke methods indirectly. Let's make an example:

At first, we need to declare a delegate.

class DeclaringASimpleDelegate
{
  // Declaration of a delegate
  delegate int MathOperation(int x, int y);
}

After that, we can create a method with a similar signature to our delegate. Note that both the delegate and the method signature accept two integer inputs and return an integer input.

class DeclaringASimpleDelegate
{
  // Declaration of a delegate
  delegate int MathOperation(int x, int y);

  // Method that matches the delegate signature
  static int AddOperation(int number1,int number2)
  {
    return number1 + number2;
  }
}

Then, we can create an instance of the delegate and pass our method to it. As you can see, by passing numbers to the instance of the delegate we can invoke our method.

class DeclaringASimpleDelegate
{
    // Declaration of a delegate
    delegate int MathOperation(int x, int y);

    public static void Run()
    {
        // Create an instance of the delegate
        MathOperation mathOperationDelegate = new MathOperation(AddOperation);

        // Invoke the delegate
        int opereationReult = mathOperationDelegate(2, 3);
        // Returns 5

        // Another way to invoke the delegate using the Invoke method
        opereationReult = mathOperationDelegate.Invoke(2, 3);
        // Returns 5
    }

    // Method that matches the delegate signature
    static int AddOperation(int number1, int number2)
    {
        return number1 + number2;
    }
}

Lambdas

In C#, a lambda expression is a concise way to represent an anonymous method, essentially allowing you to define a delegate inline. Lambda expressions are particularly useful for writing code more compactly and for situations where you need a short-lived function without explicitly defining a separate named method. The basic syntax of a lambda expression looks like this:

(parameters) => expression

Here's a simple example that demonstrates the syntax and usage of a lambda expression using the previous example. Note that here we no longer have the AddOperation method.

class UsingASimpleLambdaInsteadOfInstantiatingDelegate
{
    // Declaration of a delegate
    delegate int MathOperation(int x, int y);

    public static void Run()
    {
        // Use a lambda instead of Instantiating the delegate 
        MathOperation mathOperationDelegate = (a, b) => a + b;

        // Invoke the delegate
        int opereationReult = mathOperationDelegate(2, 3);
        // Returns 5

        // Another way to invoke the delegate using the Invoke method
        opereationReult = mathOperationDelegate.Invoke(2, 3);
        // Returns 5
    }
}

If we want to go one step further and remove the delegate declaration part, we can use generic delegate types. In C#, Func and Action are generic delegate types provided by the .NET framework. They are part of the System namespace and are commonly used to represent delegates without explicitly declaring a custom delegate type. Both Func and Action are predefined generic delegate types with a specific number of input parameters and a return type.

Func: Func is a generic delegate type representing a function with input parameters and a return type. The last type parameter represents the return type, and the preceding parameters represent the input parameters.

Again, we use our initial example to describe Func. As you can see, we rid ourselves of declaring the delegate. The code becomes even more simple.

class UsingFuncGenericDelegateTypeInsteadOfDelegateType
{
    public static void Run()
    {
        // Use a func generic delegate type instead of delegate type
        Func<int, int, int> mathOperationDelegate = (a, b) => a + b;

        // Invoke the delegate
        int opereationReult = mathOperationDelegate(2, 3);
        // Returns 5

        // Another way to invoke the delegate using the Invoke method
        opereationReult = mathOperationDelegate.Invoke(2, 3);
        // Returns 5
    }
}

Action: Action is a generic delegate type representing a method with input parameters but no return type (void). The type parameters represent the input parameters of the method.

Example: Action<string, string> represents a method that takes two strings as input but doesn't return anything. It just concatenates them and then print the result.

class UsingActionGenericDelegateType
{
    public static void Run()
    {
        // Use an action generic delegate type
        Action<string, string> printFullName = (firstName, lastName) =>
            Console.WriteLine($"Your fullname is: {firstName} {lastName}");

        // Invoke the delegate
        printFullName("Khosro", "Pakmanesh");
        // Prints 'Your fullname is: Khosro Pakmanesh'

        // Another way to invoke the delegate using the Invoke method
        printFullName.Invoke("Khosro", "Pakmanesh");
        // Prints 'Your fullname is: Khosro Pakmanesh'
    }
}

Using Func and Action can help remove the need for explicitly declaring custom delegate types in certain scenarios, making the code more concise and readable. They are often used in scenarios where you need to pass functions as parameters or return them from methods.

Normal Expressions and Expression Trees

In C#, an expression tree is a data structure that represents a lambda expression or any expression as a tree-like structure at runtime. It captures the code as data that can be examined, analyzed, and manipulated at runtime. This is in contrast to normal expressions in C#, which are evaluated directly during runtime. When you create a lambda expression using the => syntax, you can choose to treat it as a normal expression or as an expression tree, depending on the context. If you declare a delegate or use the expression directly in a LINQ query, it is treated as a normal expression and is evaluated at runtime. If you assign the lambda expression to an Expression<TDelegate> type, you are treating it as an expression tree. This allows you to manipulate the code, analyze it, and even compile it into executable code at runtime. Here's an example to illustrate the difference:

class NormalExpressionVsExpressionTree
{
  static void Run()
  {
    // Normal expression
    Func<int, int, int> mathOperationDelegate = (a, b) => a + b;
    int result = mathOperationDelegate(2, 3); // Directly evaluates at runtime
    // Returns 5
    
    // Expression tree
    // Example of an expression tree for the same addition operation
    Expression<Func<int, int, int>> mathOperationExpressionTree = (a, b) => a + b;
    
    // Compilation of the expression tree into a delegate
    Func<int, int, int> mathOperationDelegate2 = mathOperationExpressionTree.Compile();
    
    // Invoke the delegate
    int opereationReult = mathOperationDelegate2(2, 3);
    // Returns 5
  }
}

In the example, mathOperationDelegate is a standard delegate that directly evaluates the expression at runtime, while mathOperationExpressionTree is an expression tree that can be manipulated and compiled before evaluation. Note that in this example we still have not manipulated the addition operation. Moreover, the compile method compiles an expression tree into a delegate. Now, we want to add some code that changes the expression tree the way that it subtracts 5 from the result of the addition operation.

class AdditionOperationExpressionTree (Before)
{
    public static void Run()
    {
        // Example of an expression tree for the same addition operation
        Expression<Func<int, int, int>> mathOperationExpression = (a, b) => a + b;

        // Compilation of the expression tree into a delegate
        Func<int, int, int> mathOperationDelegate = mathOperationExpression.Compile();

        // Invoke the delegate
        int opereationReult = mathOperationDelegate(2, 3);
        // Returns 5
    }
}
class ManipulatedAdditionOperationExpressionTree (After)
{
    public static void Run()
    {
        // Example of an expression tree for the same addition operation
        Expression<Func<int, int, int>> mathOperationExpressionTree = (a, b) => a + b;

        // Subtract 5 from the result expression
        var modifiedExpressionTree = Expression.Lambda<Func<int, int, int>>(
            Expression.Subtract(mathOperationExpressionTree.Body, Expression.Constant(5)),
            mathOperationExpressionTree.Parameters);

        // Compilation of the modified expression tree into a delegate
        Func<int, int, int> modifiedMathOperationDelegate = modifiedExpressionTree.Compile();

        // Invoke the modified delegate
        int operationResult = modifiedMathOperationDelegate(3, 4);
        // Returns 2
    }
}

This method demonstrates the use of expression trees to create, modify, and compile a mathematical operation. Let's break down the code step by step:

Expression<Func<int, int, int>> mathOperationExpressionTree = (a, b) => a + b; 

Expression Tree Creation: This line creates an expression tree representing a binary addition operation between two parameters (a and b). The result of this addition is of type int.

var modifiedExpressionTree = Expression.Lambda<Func<int, int, int>>( Expression.Subtract(mathOperationExpressionTree.Body, Expression.Constant(5)), mathOperationExpressionTree.Parameters); 

Expression Tree Modification: This part modifies the original expression tree. It subtracts a constant value of 5 from the original addition expression. The modified expression is then wrapped in a lambda expression with the same parameter types.

Func<int, int, int> modifiedMathOperationDelegate = modifiedExpressionTree.Compile(); 

Expression Tree Compilation: This line compiles the modified expression tree into a delegate of type Func<int, int, int>. The resulting delegate can be invoked as a regular function.

int operationResult = modifiedMathOperationDelegate(3, 4); 

Delegate Invocation: The compiled delegate is invoked with the arguments 3 and 4. This results in the modified mathematical operation, which is the original addition operation subtracted by 5.

Console.WriteLine($"Result after subtraction: {operationResult}"); 

Finally, the result of the modified mathematical operation is printed to the console, which is 2.

Now that you have become familiar with the normal expressions and expression trees. It would be a good idea to review some related concepts using the following example:

class ExpressionConcepts
{
    public static void Run()
    {
        // Example of MemberExpression
        MemberExpression memberExpression = Expression.Field(null, typeof(int), nameof(int.MaxValue));
        Expression<Func<int>> intMaxValueExpressionTree = Expression.Lambda<Func<int>>(memberExpression);
        var maxValueDelegate1 = intMaxValueExpressionTree.Compile();
        var operationResult = maxValueDelegate1.Invoke();
        // Returns 2147483647

        // Example of ParameterExpression
        ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "x");
        Expression<Func<int, int>> squareExpressionTree = Expression.Lambda<Func<int, int>>(
            Expression.Multiply(parameterExpression, parameterExpression), parameterExpression);
        var squareDelegate = squareExpressionTree.Compile();
        operationResult = squareDelegate.Invoke(5);
        // Returns 25

        // Example of ConstantExpression
        ConstantExpression constantExpression = Expression.Constant(6);
        Expression<Func<int, int>> addConstantExpressionTree = Expression.Lambda<Func<int, int>>(
            Expression.Add(parameterExpression, constantExpression), parameterExpression);
        var addConstantDelegate = addConstantExpressionTree.Compile();
        operationResult = addConstantDelegate.Invoke(5);
        // Returns 11

        // Example of Expression.Equal
        BinaryExpression binaryExpression = Expression.Equal(parameterExpression, constantExpression);
        Expression<Func<int, bool>> isEqualExpressionTree = Expression.Lambda<Func<int, bool>>
            (binaryExpression, parameterExpression);
        var isEqualDelegate = isEqualExpressionTree.Compile();
        var booleanOperationResult = isEqualDelegate.Invoke(6);
        // Returns True

        // Example of MethodCallExpression
        var equalsMethodInfo = typeof(int).GetMethod("Equals", new[] { typeof(int) });
        var intConstantExpression1 = Expression.Constant(5);
        var intConstantExpression2 = Expression.Constant(5);
        MethodCallExpression methodCallExpression = Expression.Call(
            intConstantExpression1,
            equalsMethodInfo,
            intConstantExpression2
        );
        Expression<Func<bool>> lambdaExpression = 
            Expression.Lambda<Func<bool>>(methodCallExpression);
        var equalsDelegate = lambdaExpression.Compile();
        booleanOperationResult = equalsDelegate.Invoke();
        // Returns True
    }
}

MemberExpression: MemberExpression is part of the Expression Tree API in C#. It represents accessing a field or property of a type. It is commonly used to build expression trees that involve accessing members (fields or properties) of objects.

ParameterExpression: ParameterExpression is used to declare a parameter in an expression tree. It represents a named parameter in a lambda expression or another form of expression tree, and it defines the input parameters for the expression.

ConstantExpression: ConstantExpression represents a constant value within an expression tree. It is used to embed fixed values directly into the expression tree.

BinaryExpression: BinaryExpression represents a binary operation, such as addition, subtraction, multiplication, etc. It typically involves two operands and an operator.

MethodCallExpression: MethodCallExpression represents a call to a method. It is used to build expression trees that involve method calls.

Expression.Equal: Expression.Equal creates a BinaryExpression that represents an equality comparison. It's commonly used to build expression trees for equality checks.

Expression.Lambda: Expression.Lambda is used to create a lambda expression in the form of an expression tree. It defines a lambda expression with a specified body and parameters.

In summary, lambda expressions are a concise way to write anonymous functions in C#, and when used in contexts that expect expression trees, they are automatically converted into expression trees. Expression trees, on the other hand, provide a way to represent code as data, which is particularly useful in scenarios where you need to manipulate or analyze code dynamically.