views:

94

answers:

4

I have the need for an object to object mapper in my application. I've tried out a few, but haven't been able to find anything that fits my needs, so I'm writing my own. Currently I have an interface like below:

public interface IMapper<T, R> {
    T Map(R obj);
}

I then implement an AccountMapper that maps a Customer to an Account as:

public class AccountMapper : IMapper<Account, Customer> {
    Account Map(Customer obj) {
        // mapping code
    }
}

This works fine so far, however I have several source entities that map to the same destination entity. For instance I have a Payment and an Invoice that both map to BillHistory. For the above to support this, I need to make two separate mappers (ie. BillHistoryPaymentMapper and BillHistoryInvoiceMapper), which is fine. However, I'd love to be able to implement it slightly differently like below. Only problem is I don't know if it's possible and if so, I don't know the correct syntax.

public interface IMapper<T> {
    T Map<R>(R obj);
}

public class BillHistoryMapper : IMapper<Account> {
    public BillHistory Map<Invoice>(Invoice obj) {
        // mapping code
    }
    public BillHistory Map<Payment>(Payment obj) {
        // mapping code
    }
}

While the first implementation works fine, the second would be slightly more elegant. Is this possible and if so what would the correct syntax look like?

edit-------

I hate when people do this, but of course I forgot to mention one little detail. We have an abstract class between the mapper and the interface to implement some common logic across all of the mappers. So my mapper signature is actually:

public class BillHistoryMapper : Mapper<BillHistory, Invoice> {
}

where Mapper contains:

public abstract class Mapper<T, R> : IMapper<T, R> {
    public IList<T> Map(IList<R> objList) {
        return objList.ToList<R>().ConvertAll<T>(new Converter<T, R>(Map));
    }
}
A: 

Your second example will work with only a few changes:

// you have to include the R type in the declaration of the Mapper interface
public interface IMapper<T, R> {
    T Map<R>(R obj);
}

// You have to specify both IMapper implementations in the declaration
public class BillHistoryMapper : IMapper<Account, Invoice>, IMapper<Account, Payment> {
    public BillHistory Map<Invoice>(Invoice obj) {
        // mapping code
    }
    public BillHistory Map<Payment>(Payment obj) {
        // mapping code
    }
}

I'm not sure if this actually gains you anything over the existing pattern, though.

JSBangs
A: 

If there is a limited number of types you want to map from, then I would use the first method of declaring the input and output types in the interface definition. Then a mapper can implement interfaces for each input type it supports, so your BillHistoryMapper would be declared as:

public class BillHistoryMapper : IMapper<BillHistory, Invoice>, IMapper<BillHistory, Payment>
{
   ...
}
Lee
+2  A: 

You'll have to use your first interface and implement the interface multiple times on your object:

public class BillHistoryMapper : IMapper<Account, Invoice>, 
                                 IMapper<Account, Payment> {     
   ...
}

I would serious consider taking a look at AutoMapper instead of writing your own. There are a lot of nuances in mapping that it has already solved, not to mention it has been through plenty of performance testing, bug fixes, etc.

Eric Hauser
see edit above, code didn't display correctly in the comment
Brian
You code implement your Map method as an extension method on the IMapper interface. That way you don't have to use an abstract base class.
Eric Hauser
I've marked this as the answer b/c it answered my original question. I was also able to accomplish this without an abstract class using Eric's suggestion. Thanks!!!
Brian
A: 

With regard to your abstract class consider getting rid of it and replacing it with an extension method:

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));
    }
}

Also consider changing your IMapper generic parameters to be the otherway round:

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

The reason for this is that it directly maps to the System.Converter<T> delegate and you can do something like:

IMapper<ObjectA, ObjectB> myAToBMapper = new MyAToBMapper();

ObjectA[] aArray = { new ObjectA(), new ObjectA() };
ObjectB[] bArray = Array.ConvertAll<ObjectA, ObjectB>(aArray, myAToBMapper.Map);

List<ObjectA> aList = new List<ObjectA> { new ObjectA(), new ObjectA() };
List<ObjectB> bList = aList.ConvertAll<ObjectB>(myAToBMapper.Map);

// Or

var aToBConverter = new Converter<ObjectA, ObjectB>(myAToBMapper.Map);
bArray = Array.ConvertAll(aArray, aToBConverter);
bList = aList.ConvertAll(aToBConverter);
Bronumski