LINQ Formatters
Tags: csharp, linq, expressions, webdev
Published: 2022-08-08 11:34:00

Formatting is a common task. Normally one would like to have the same formatting rule across the system without needing to copy/paste the same rules. This is how you can easily do it in C#.

First of all, formatting rules can be applied to objects located in memory or to database queries. Ideally, the tool must support both scenarios. Luckily expression trees can do the job for us. When you write code like:

```cs
queryable.Select(x => x.FirstName  + " " + x.LastName).ToList();
// Expression<Func<Employee, String>>
```

and

```cs
enumerable.Select(x => x.FirstName  + " " + x.LastName).ToList();
// Func<Employee, String>
```

the type of `x => x.Name` lambdas are different, despite they look the same at first glance. Check my talk about [expression trees] (https://www.youtube.com/watch?v=ncdRDv6sV1A&ab_channel=NDCConferences) to better understand the difference if you like.

For our purpose, we need the `Expression<Func<Employee, String>>` type.

```cs
public class Formatter<T>
{
   private readonly Expression<Func<T, string>> _expression;

   public Formatter(Expression<Func<T, string>> expression)
   {
       _expression = expression
       ?? throw new ArgumentNullException(nameof(expression));
   }

    public static implicit operator
    Formatter<T>(Expression<Func<T, string>> expresssion)=>
    new Formatter<T>(expresssion);

    public static implicit operator
    Expression<Func<T, string>>(Formatter<T> formatter) =>
    formatter._expression;

    public string Format(T obj) =>
    (_func ?? (_func = _expression.Compile())).Invoke(obj);
}
```

Here we override conversions from/to corresponding expressions, so that both:

```cs
Formatter<Employee> formatter =
  (Expression<Func<Employee, String>>)
  x => x.FirstName  + " " + x.LastName;
```

and

```cs
queryable.Select(formatter).ToList();
```

work just fine.

To use it on in-memory objects we are going to use the `Format` function

```cs
string formattedLastName = formatter.Format(obj);
```

## Managing Hierarchy
So far so good. However, what if needed to use the same formatting for the employee account?

```cs
public class Account 
{
    public Employee Employee { get; set; }
}
```

We could use something like:

```cs
accounts.Select(x => employeeFormatter.Format(x.Employee));
```

However, this code will not be translated properly, because it captures [`InvocationExpression`](https://docs.microsoft.com/en-us/dotnet/api/system.linq.expressions.invocationexpression?view=net-6.0) while what we need is an expression that contains the body of the format message. Long story short, we need a way to build another expression from the target expression.

```cs
public Formatter<TParent> From<TParent
  (Expression<Func<TParent, T>> map) =>
  new Formatter<TParent>(
    Expression.Lambda<Func<TParent, string>>(
      Expression.Invoke(_expression, map.Body),
      map.Parameters.First()));
```

Please check the [talk](https://www.youtube.com/watch?v=ncdRDv6sV1A&ab_channel=NDCConferences) if you don’t understand this paragraph. This requires some code plumbing, so there is no simple explanation of this issue. 

With the `From` method now we can build an `AccountFormatter` using the Employee formatter.

```cs
var employeeFormatter = (Expression<Func<Employee, String>>)
    x => x.FirstName  + " " + x.LastName;

var accountFormatter = 
    employeeFormatter.From(x => x.Employee);

accounts.Select(accountFormatter).ToList();
```

## Data mappers (Automapper/Mapster/etc)
If you are a fan of data mappers, you might want to enhance the implementation with additional extension methods. Here is an example for [AutoMapper](https://automapper.org/):

```cs
public static class AutomapperFormatterExtensions
{
   public static void FormatWith<TSource, TDest>(
       this IMemberConfigurationExpression<TSource, TDest, string> mapperConfiguration,
       Formatter<TSource> formatter) =>
       mapperConfiguration.MapFrom<string>(formatter);
  
   public static void FormatWith<TSource, TDest>(
       this IPathConfigurationExpression<TSource, TDest, string> mapperConfiguration,
       Formatter<TSource> formatter) =>
       mapperConfiguration.MapFrom<string>(formatter);

   public static Action<IMemberConfigurationExpression<TSource, TDest, string>> ToMapping<TSource, TDest>(
       this Formatter<TSource> formatter) =>
       x => x.FormatWith(formatter);

   public static IMappingExpression<TSource, TDestination> ForMember<TSource, TDestination>(
       this IMappingExpression<TSource, TDestination> mappingExpression,
       Expression<Func<TDestination, string>> destinationMember,
       Formatter<TSource> formatter) =>
       mappingExpression.ForMember(
         destinationMember,
         formatter.ToMapping<TSource, TDestination>());

   public static IMappingExpression<TSource, TDestination> ForName<TSource, TDestination>(
       this IMappingExpression<TSource, TDestination> mappingExpression,
       Formatter<TSource> formatter) =>
       where TDestination : IHasName
       mappingExpression.ForMember(
         x => x.Name,
         formatter.ToMapping<TSource, TDestination>());
}
```

The complete solution would look like:

```cs
public class Formatter<T>
{
   private readonly Expression<Func<T, string>> _expression;
   private Func<T, string> _func { get; set; }

   public Formatter(Expression<Func<T, string>> expression)
   {
       _expression = expression ?? throw new ArgumentNullException(nameof(expression));
   }

   public Formatter<TParent> From<TParent>(Expression<Func<TParent, T>> map)
       => new Formatter<TParent>(Expression.Lambda<Func<TParent, string>>(
           Expression.Invoke(_expression, map.Body), map.Parameters.First()));

   public static implicit operator Formatter<T>(Expression<Func<T, string>> expresssion) =>
       new Formatter<T>(expresssion);

   public static implicit operator Expression<Func<T, string>>(Formatter<T> formatter) => formatter._expression;

   public string Format(T obj) => (_func ?? (_func = _expression.AsFunc())).Invoke(obj);
}

public static class AutomapperFormatterExtensions
{
   public static void FormatWith<TSource, TDest>(
       this IMemberConfigurationExpression<TSource, TDest, string> mapperConfiguration,
       Formatter<TSource> formatter) =>
       mapperConfiguration.MapFrom<string>(formatter);
  
   public static void FormatWith<TSource, TDest>(
       this IPathConfigurationExpression<TSource, TDest, string> mapperConfiguration,
       Formatter<TSource> formatter) =>
       mapperConfiguration.MapFrom<string>(formatter);

   public static Action<IMemberConfigurationExpression<TSource, TDest, string>> ToMapping<TSource, TDest>(
       this Formatter<TSource> formatter) =>
       x => x.FormatWith(formatter);

   public static IMappingExpression<TSource, TDestination> ForMember<TSource, TDestination>(
       this IMappingExpression<TSource, TDestination> mappingExpression,
       Expression<Func<TDestination, string>> destinationMember,
       Formatter<TSource> formatter) =>
       mappingExpression.ForMember(destinationMember, formatter.ToMapping<TSource, TDestination>());

   public static IMappingExpression<TSource, TDestination> ForName<TSource, TDestination>(
       this IMappingExpression<TSource, TDestination> mappingExpression,
       Formatter<TSource> formatter) =>
       where TDestination : IHasName
       mappingExpression.ForMember(x => x.Name, formatter.ToMapping<TSource, TDestination>());
}

```
Happy coding!