106  
csharp
Поиск  
Always will be ready notify the world about expectations as easy as possible: job change page
May 31, 2023

Expressions in .NET: A practical guide to System.Linq.Expressions

Автор:
Anton Selin
Источник:
Просмотров:
6764

Expressions in .NET: A Practical guide to System.Linq.Expressions

LINQ (Language Integrated Query) is a powerful querying tool in .NET that allows you to perform complex queries directly in C#.

The System.Linq.Expressions namespace is a part of LINQ that provides classes, interfaces, enumerations and structures to work with lambda expressions and expression trees.

Lambda expressions are a way to create anonymous methods inline where they’re used, usually with the purpose of manipulating data. For example, let’s say you have a list of integers and you want to find all the even numbers. You could use a lambda expression like this:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = numbers.Where(n => n % 2 == 0);

In this example, n => n % 2 == 0 is the lambda expression. It defines an anonymous method that takes a parameter n and returns whether n is even.

An expression tree, on the other hand, is a data structure that represents some code. Unlike lambda expressions, expression trees can be analyzed and transformed at runtime.

Simple Expressions

First, let’s create a very simple expression tree.

using System;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        // Build an expression tree that represents '5 + 10'
        Expression<Func<int>> sum = () => 5 + 10;

        // Compile and run the expression
        Console.WriteLine(sum.Compile().Invoke());  // Outputs: 15
    }
}

Here, we’re creating an expression tree that represents the sum of 5 and 10. This is a very simple example and doesn’t demonstrate the power of System.Linq.Expressions, but it’s a good place to start.

Parameterized Expressions

Expressions become much more powerful when you include parameters.

using System;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        // Build an expression tree that represents 'x + y'
        Expression<Func<int, int, int>> sum = (x, y) => x + y;

        // Compile and run the expression
        Console.WriteLine(sum.Compile().Invoke(5, 10));  // Outputs: 15
    }
}

Now, instead of a static sum of 5 and 10, we have an expression tree that represents the sum of two parameters. We can compile and invoke this expression with any two integers.

Building Expression Trees

Building expression trees manually gives you even more flexibility. Here’s how you can build the ‘x + y’ expression tree manually.

using System;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        // Parameters
        ParameterExpression x = Expression.Parameter(typeof(int), "x");
        ParameterExpression y = Expression.Parameter(typeof(int), "y");

        // Add expression
        BinaryExpression add = Expression.Add(x, y);

        // Lambda
        Expression<Func<int, int, int>> sum = Expression.Lambda<Func<int, int, int>>(add, new ParameterExpression[] { x, y });

        // Compile and run
        Console.WriteLine(sum.Compile().Invoke(5, 10));  // Outputs: 15
    }
}

This example gives you a taste of the power of expression trees. We’ve manually built an expression tree that represents ‘x + y’. This involves creating parameter expressions for ‘x’ and ‘y’, a binary expression that represents ‘x + y’, and a lambda expression that represents the entire ‘x + y’ expression. We can then compile and run this expression just like before.

Manipulating Expression Trees

One of the coolest things about expression trees is that you can manipulate them. Let’s say you have an expression that represents ‘x + y’, and you want to change it to ‘x + y + 1’. Here’s how you can do that:

using System;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        // Original expression
        Expression<Func<int, int, int>> sum = (x, y) => x + y;

        // Create a new expression tree that represents 'x + y + 1'
  BinaryExpression addOne = Expression.Add(sum.Body, Expression.Constant(1));

        // Update the lambda
        sum = Expression.Lambda<Func<int, int, int>>(addOne, sum.Parameters);

        // Compile and run
        Console.WriteLine(sum.Compile().Invoke(5, 10));  // Outputs: 16
    }
}

In this example, we’ve modified the original ‘x + y’ expression tree to add 1 to the result. We did this by creating a new binary expression that represents ‘x + y + 1’, and then updating the lambda to include this new expression. When we compile and run this new expression, it outputs 16.

Using Expression Trees for Filtering

A common use of expression trees is to create dynamic filters for querying data. Here’s an example of how you might do that:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        // Some data
        List<int> data = new List<int> { 1, 2, 3, 4, 5 };

        // Build a filter expression
        Expression<Func<int, bool>> filter = x => x > 2;

        // Apply the filter
        IEnumerable<int> filteredData = data.AsQueryable().Where(filter);

        // Print the filtered data
        foreach (int x in filteredData)
            Console.WriteLine(x);
    }
}

In this example, we’re using an expression tree to create a filter for data. This filter is ‘x > 2’, and it filters out any integers in the data list that are not greater than 2.

Real Life Scenarios (BONUS)

One of the main applications of expression trees is in the implementation of ORMs (Object-Relational Mappers) such as Entity Framework. These tools use expression trees to translate LINQ queries in C# into SQL queries that can be executed against a database.

Another use case is in building dynamic queries, like in the example above. This can be useful in situations where you need to build a query based on user input, for example, in a search or filtering feature.

In addition to the aforementioned use-cases, expression trees can be employed to facilitate reflection-like scenarios. For instance, if you want to read or manipulate metadata about a class or method, but find that using reflection is too slow or cumbersome, you can use expression trees to build and compile code dynamically. This approach can provide a performance benefit because, unlike reflection, the dynamic code gets compiled and can be optimized by the JIT compiler.

Property Getter/Setter

Consider a scenario where you need to access a property on an object, but you don’t know which property until runtime. You can use expression trees to create a getter and setter for a property dynamically:

public static Func<T, TResult> MakeGetter<T, TResult>(string propertyName)
{
    var parameter = Expression.Parameter(typeof(T), "obj");
    var property = Expression.Property(parameter, propertyName);
    var lambda = Expression.Lambda<Func<T, TResult>>(property, parameter);
    
    return lambda.Compile();
}

public static Action<T, TValue> MakeSetter<T, TValue>(string propertyName)
{
    var parameter = Expression.Parameter(typeof(T), "obj");
    var value = Expression.Parameter(typeof(TValue), "value");
    var property = Expression.Property(parameter, propertyName);
    var assign = Expression.Assign(property, value);
    var lambda = Expression.Lambda<Action<T, TValue>>(assign, parameter, value);
    
    return lambda.Compile();
}

Now you can use these methods to get or set a property at runtime:

var user = new User { Name = "Alice", Age = 25 };

var getName = MakeGetter<User, string>("Name");
var setName = MakeSetter<User, string>("Name");

Console.WriteLine(getName(user));  // Outputs: Alice

setName(user, "Bob");
Console.WriteLine(getName(user));  // Outputs: Bob

As you can see, System.Linq.Expressions is a powerful tool for manipulating code at runtime. It’s at the heart of many .NET technologies and can help you write more flexible and dynamic code.

Похожее
Apr 24, 2022
Author: HungryWolf
What is MediatR? Why do we need it? And How to use it? Mediator Pattern - The mediator pattern ensures that objects do not interact directly instead of through a mediator. It reduces coupling between objects which makes it easy...
Jul 25, 2023
Author: Anthony Trad
Bending the Clean Architecture Principles Async await meme Introduction Imagine you’re a chef in a kitchen full of ingredients, some fresh, some a bit past their prime, all thanks to Microsoft’s “We never throw anything away” policy. This is what...
Feb 10, 2023
Author: Hr. N Nikitins
Design patterns are essential for creating maintainable and reusable code in .NET. Whether you’re a seasoned developer or just starting out, understanding and applying these patterns can greatly improve your coding efficiency and overall development process. In this post, we’ll...
Nov 30, 2023
Author: Dev·edium
Keeping your C# applications safe and sound. Keeping app secrets safe is always tricky for developers. We want to work on the main parts of the app without getting distracted by secret-keeping. But, the app’s safety is very important. So,...
Написать сообщение
Тип
Почта
Имя
*Сообщение
RSS