in

 

GrabBag<T>

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.

Comments

 

Neil Mosafi said:

That is nice, but you could have avoided the creation of the extra list without using LINQ, by yielding, as follows:

   public static IEnumerable<TOutput> MapAll<TInput, TOutput>(this IMapper<TInput, TOutput> mapper,

       IEnumerable<TInput> input)

   {

       foreach (TInput x in input)

           yield return mapper.Map(x);

   }

May 8, 2008 8:30 AM
 

bogardj said:

@Neil

You could definitely do that.  But remember, the Select LINQ query operator does that for you as it has deferred query execution.  The LINQ query operators use the yield operator all over the dang place.

May 8, 2008 9:17 AM
 

Colin Jack said:

Good stuff, makes me wonder whether every codebase has an IMapper<TInput, TOutput> style interface.

May 8, 2008 10:01 AM
 

Jarod said:

I prefer"

GetSalesOrders()

    .Select(SalesOrderSummaryMapper.Map)

    .ToArray();

Even less code and no extra extension method

May 8, 2008 11:37 AM
 

Alexey Romanov said:

Why would you prefer IMapper<TInput, TOutput> over

delegate TOutput Map<TInput, TOutput>(TInput input)?

And then notice that this delegate already exists in the framework (two times, yet!).

The only thing I can suppose is that you want to have a name for a reused mapper. But in this case you can easily do

public static class SalesOrderSummaryMapper {

   public static readonly Func<SalesOrder, SalesOrderSummary> Map =

        input => new SalesOrderSummary(input.Customer.Name, input.Customer.Id, input.Total;

}

or

public static partial class Mappers {

   public static readonly Func<SalesOrder, SalesOrderSummary> SalesOrderSummaryMap =

        input => new SalesOrderSummary(input.Customer.Name, input.Customer.Id, input.Total;

//other mappers

}

May 8, 2008 3:55 PM
 

Doug Koehn said:

Using the System.Converter<TInput, TOutput> delegate.

All you need is the following extension method:

public static IEnumerable<TOutput> ConvertAll<TInput, TOutput>(this IEnumerable<TInput> list, Converter<TInput, TOutput> converter)

{

   foreach (TInput v in list)

       yield return converter.Invoke(v);

}

May 9, 2008 12:19 AM
 

Dew Drop - May 9, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - May 9, 2008 | Alvin Ashcraft's Morning Dew

May 9, 2008 7:48 AM
 

bogardj said:

@Alexey, Doug

The problem with a delegate by itself is that it's difficult to reuse.  If I wanted to go that approach, I'd have two items: a method to perform the mapping, and the delegate field to point to the map method.

I've done something similar with specifications, which was clever, but didn't really lead to "intention-revealing interfaces".

May 9, 2008 8:01 AM
 

GrabBag said:

Now that .NET 3.5 is out with all its LINQ query operator goodness, I feel like going on a mean streak

May 9, 2008 6:11 PM
 

Scott Bellware said:

"IEnumerable<TOutput> MapAll<TInput, TOutput>(this IMapper<TInput, TOutput> mapper, IEnumerable<TInput> input)"

This looks like LoC optimization at the sake of solubility.  Had to read that signature a couple of times to really assimilate it.  I'd go back to the previous implementation.  It's cool and all, but to me, it's merely cool.

May 11, 2008 12:04 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About bogardj

I'm a senior consultant with Headspring Systems in Austin, TX. My focus is using .NET technologies together with Agile methodologies. Back in 2005, I drank the Agile punch and haven't looked at a waterfall the same since.
Copyright Los Techies 2007. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems