Enhancing mappers with LINQ


The “big 3” higher-order functions in functional programming are Filter, Map and Reduce.  When looking at the new C# 3.0 LINQ query operators, we find that all three have equivalents:

  • Filter = Where
  • Map = Select
  • Reduce = Aggregate

Whenever you find yourself needing one of these three higher-order functions, just translate them into the correct query operator.  “Select” doesn’t have the same dictionary meaning as “Map”, but the signature is exactly the same.

The trick to knowing you can use these higher order functions is to look out for situations where you:

  1. Create a new collection
  2. Iterate through some other collection
  3. Add items from the other collection to the new collection

Any time you see this general algorithm, there’s a much terser syntax available with LINQ.

###

Mapper patter example

For example, consider the Mapper pattern:

public interface IMapper<TInput, TOutput>
{
    TOutput Map(TInput input);
}

A common scenario to map is when I’m creating DTOs or message objects from Domain objects.  Serializing domain objects generally isn’t a concern of the domain object, as DTOs tend to be flattened out somewhat.  I might have the following domain objects:

public class Customer
{
    public Guid Id  { get; set; }
    public string Name { get; set; }
}
public class SalesOrder
{
    public Customer Customer { get; set; }
    public decimal Total { get; set; }
}

I’d like to send a Sales Order over the wire for display in some client application.  But suppose the Customer object contains dozens of properties, perhaps things like a billing address, a shipping address, and so on.  The service I’m creating only needs a summary of customer information, so I create a SalesOrderSummary message class:

public class SalesOrderSummary
{
    public string CustomerName { get; private set; }
    public Guid CustomerId { get; private set; }
    public decimal Total { get; private set; }

    // For serialization
    private SalesOrderSummary() { }

    public SalesOrderSummary(string customerName, Guid customerId, decimal total)
    {
        CustomerName = customerName;
        CustomerId = customerId;
        Total = total;
    }
}

The corresponding mapper would look like:

public class SalesOrderSummaryMapper : IMapper<SalesOrder, SalesOrderSummary>
{
    public SalesOrderSummary Map(SalesOrder input)
    {
        return new SalesOrderSummary(input.Customer.Name, input.Customer.Id, input.Total);
    }
}

Nothing too exciting so far, right?  Well, suppose now I need to return an array of SalesOrderSummary, perhaps for display in a grid.  In that case, I’ll need to build up a list of SalesOrderSummary objects based on a list of SalesOrder objects:

public SalesOrderSummary[] FindSalesOrdersByMonth(DateTime date)
{
    // get the sales orders from the repository first
    IEnumerable<SalesOrder> salesOrders = GetSalesOrders();

    var salesOrderSummaries = new List<SalesOrderSummary>();
    var mapper = new SalesOrderSummaryMapper();

    foreach (var salesOrder in salesOrders)
    {
        salesOrderSummaries.Add(mapper.Map(salesOrder));
    }

    return salesOrderSummaries.ToArray();
}

This isn’t too bad, but the creation of a second list just to build up an array seems rather pointless.  But by borrowing some ideas from JP, we can make this a lot easier.

Using LINQ

We can already see the higher order function we need, it’s in the name of the mapper!  Instead of “Map”, we’ll use “Select” to do the transforming.  But since we have the interface, we can create an extension method to do the Map function:

public static class MapperExtensions
{
    public static IEnumerable<TOutput> MapAll<TInput, TOutput>(this IMapper<TInput, TOutput> mapper, 
        IEnumerable<TInput> input)
    {
        return input.Select(x => mapper.Map(x));
    }
}

This new MapAll function is the functional programming Map function, where it takes an input list and returns a new IEnumerable with the mapped values.  Internally, the Select LINQ query operator will loop through our input, calling the lambda function we passed in (mapper.Map).  This is the exact same operation in our original example, but now our service method now becomes much smaller:

public SalesOrderSummary[] FindSalesOrdersByMonth(DateTime date)
{
    // get the sales orders from the repository first
    IEnumerable<SalesOrder> salesOrders = GetSalesOrders();
    var mapper = new SalesOrderSummaryMapper();

    return mapper.MapAll(salesOrders).ToArray();
}

Much nicer, our service method is reduced to just a handful of lines.  The nice thing about this syntax is that it removes all of the unnecessary cruft of a temporary list creation that clouded the intent of this method.

So any time you find yourself creating a temporary list just to build up some filtered, mapped or reduced values, stop yourself.  There’s a higher calling available with functional programming and LINQ.

Mike Cohn in town