Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# generics based object to object mapper question

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));
    }
}
like image 248
Brian Avatar asked May 13 '10 19:05

Brian


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.


2 Answers

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.

like image 158
Eric Hauser Avatar answered Oct 07 '22 13:10

Eric Hauser


With regard to your abstract class consider getting rid of it and replacing it with an extension method. This will allow you to use the MapAll function regardless of whether you implement the interface or use some sort of inheritance chain.

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 will now make it easier when trying to solve your problem above because you no longer have to inherit from a base class you can now implement the mapping interface for the types you want to map.

public class BillHistoryMapper :
    IMapper<Invoice, BillHistory>, IMapper<Payment, BillHistory>
{
    public BillHistory Map<Invoice>(Invoice obj) {}
    public BillHistory Map<Payment>(Payment obj) {}
}

Also consider changing your IMapper generic parameters to be the other way round (I took the liberty in the previous examples):

public interface IMapper<in TInput, out 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);

AutoMapper has also been suggested which will make your life easier. However if you wanted to keep your mapping abstraction and have your code agnostic to your mapping strategy then it is very easy to use the above interface to inject a wrapper around AutoMapper. It will also mean you can continue to use the MapAll extension method explained above.

public class AutoMapperWrapper<in TInput, out TOutput> : IMapper<TInput, TOutput>
{
    public TOutput Map(TInput input)
    {
        return Mapper.Map<TOutput>(input);
    }
}

Final word

Also keep in mind you are not always going to find that your mapping strategy will work across the board so don't try to fight your domain and force it to fit your mapping strategy. One particular example is you might have to map from two input items into one. You can obviously make this fit your strategy but you may find it becomes messy. In this particular example consider it a merge.

like image 37
Bronumski Avatar answered Oct 07 '22 14:10

Bronumski