Click here to Skip to main content
15,882,114 members
Articles / Programming Languages / C# 4.0

Generic Type Mapping

Rate me:
Please Sign up or sign in to vote.
4.42/5 (10 votes)
18 Dec 2010CPOL3 min read 38.7K   526   20   11
A utility to assign the values of one type to another.

Background

I recently came across the need to be able to assign values of one type to values of another type that I had no control over. A vendor had provided an assembly which exposed a proxy that accepted message types that had a vastly different structure to the types we used internally. A quick view of the assembly through Reflector revealed it to be very badly written (and I do mean horrible). I wrote this little utility to allow our software to map message types we used internally to message types exposed by the third party assembly. While I implemented this for my specific needs, I thought I might as well share it with the rest of the world in the event that somebody, somewhere might face a similar problem and find it useful.

Design Goals

I wanted a simple utility with a single call to be able to assign ("map") the values of one type to another. I wanted it to be both flexible and extensible. I had considered juggling with delegates and serialization and various other tricks with Generics before I came up with what, I think, is a simple solution. I didn't want to jump through too many hoops to get the mapping done, and wanted to have as simple a call as possible at the point where the mapper was needed. I decided to list the goals as:

  1. Flexible
  2. Extensible
  3. Simple
  4. DRY (i.e., Don't Repeat Yourself)
  5. Support for IoC (although not a pre-requisite)

In the end, I ended up with something like this in my client code:

C#
Mapper mapper = new Mapper(provider);
TypeB typeB = mapper.Map<TypeB>(typeA);

How it Works

Under the hood, Mapper uses a handful of interfaces and Generics.

IMappable

Very simple, empty interface. Allows a class to be the input to the mapping. This would be the type which you do have control over. It's an empty interface deliberately so that there are no conditions to satisfy in order to use it.

C#
public interface IMappable {}

IMappingProvider

Provides the service contract for components which will provide the mapping implementation. The method expects a type argument to be supplied for its usage. This is the heart of the entire utility. It's important that the generic type is part of the method and not part of the interface declaration.

C#
public interface IMappingProvider
{
    T Map<T>(IMappable input);
}

ConcreteMappingProvider

Implements the specific mapping between IMappable to one or more types. Note: This ConcreteMappingProvider is given here only as a demonstration on very basic types. You'll notice that it implements the IMappingProvider interface with the generic <T> type expected as the result type. The interface implementation (public method) passes the work on to a private method which will return the concrete type (i.e., the type which you have no control over). It asks the private method to return the result as an object; this is because casting directly from the concrete type to <T> is illegal. Casting to <T> from object, however, is legal. Thus, you should have the expected type returned with no boxing overhead.

C#
public class ConcreteMappingProvider : IMappingProvider
{
    public T Map<T>(IMappable input)
    {
        object result = map(input as TypeA);
        return (T)result;
    }

    private TypeB map(TypeA input)
    {
        TypeB result = new TypeB();
        result.StringX = input.StringA;
        result.intY = input.IntB;
        result.StringZ = input.GuidC.ToString();
        return result;
    }
}

Mapper

Simple facade to call the specific implementation of IMappingProvider. This the facade that you call as needed to return the specifically mapped type that you are after.

C#
public class Mapper
{
    public IMappingProvider MappingProvider { get; private set; }

    public Mapper(IMappingProvider provider)
    {
        this.MappingProvider = provider;
    }

    public T Map<T>(IMappable input)
    {
        return MappingProvider.Map<T>(input);
    }

}

Sample Types

These are the sample types used in this article. Notice that TypeB does not inherit from IMappable. This would be the type that you have no control over.

C#
public class TypeA : IMappable
{
    public string StringA { get; set; }
    public int IntB { get; set; }
    public Guid GuidC { get; set; }
}

public class TypeB
{
    public string StringX { get; set; }
    public int intY { get; set; }
    public string StringZ { get; set; }
}

Dependency Injection

Although DI is not essential to using this utility, I made provision for it by separating the specific implementation from the facade. Using the DI container/framework of your choice, you can register/resolve the specific providers as you see fit. I use the Windsor container, so the download sample looks similar to:

C#
static void Main(string[] args)
{
    // set up a new container
    var container = new WindsorContainer();
    // register the service/components
    container.Register(
        Component
            .For<IMappingProvider>()
            .ImplementedBy<ConcreteMappingProvider>()
            );

    // instance of a mappable type
    TypeA typeA = new TypeA()
    {
        StringA = "some string",
        IntB = 123,
        GuidC = Guid.NewGuid()
    };

    // get the mapping provider and instantiate Mapper
    IMappingProvider provider = container.Resolve<IMappingProvider>();
    Mapper mapper = new Mapper(provider);
            
    // expect TypeB returned
    TypeB typeB = mapper.Map<TypeB>(typeA);

    Console.ReadLine();
}

Extending the Solution

To extend the solution to suit your needs, the least you'd have to do is write your own component which implements IMappingProvider. While this provider could run into a few hundred lines of code (depending on what you need to map), that is just about all you'd need to do.

Conclusion

As mentioned above, this is a lightweight type mapper which is flexible, simple, and extensible. I hope it finds a happy home in your own code.

History

Initial submission.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Ireland Ireland
Zzzzz....

Comments and Discussions

 
SuggestionWhy not just use Extension like this... Pin
Paw Jershauge11-Oct-13 0:50
Paw Jershauge11-Oct-13 0:50 
GeneralMy vote of 1 Pin
red baron23-Dec-10 18:55
professionalred baron23-Dec-10 18:55 
GeneralRe: My vote of 1 Pin
Dave Sexton9-Jan-11 12:27
Dave Sexton9-Jan-11 12:27 
GeneralMy vote of 3. Pin
James Curran19-Dec-10 16:02
James Curran19-Dec-10 16:02 
GeneralRe: My vote of 3. Pin
Dave Sexton19-Dec-10 23:15
Dave Sexton19-Dec-10 23:15 
GeneralNice idea shame there is already such a well established solution out there... [modified] Pin
Sacha Barber19-Dec-10 5:43
Sacha Barber19-Dec-10 5:43 
GeneralRe: Nice idea shame there is already such a well established solution out there... Pin
Dave Sexton19-Dec-10 7:10
Dave Sexton19-Dec-10 7:10 
GeneralRe: Nice idea shame there is already such a well established solution out there... Pin
Sacha Barber19-Dec-10 7:24
Sacha Barber19-Dec-10 7:24 
GeneralRe: Nice idea shame there is already such a well established solution out there... Pin
Dave Sexton19-Dec-10 7:29
Dave Sexton19-Dec-10 7:29 
GeneralMy vote of 5 Pin
Shahriar Iqbal Chowdhury/Galib18-Dec-10 19:27
professionalShahriar Iqbal Chowdhury/Galib18-Dec-10 19:27 
GeneralMy vote of 5 Pin
BigJim6118-Dec-10 11:47
BigJim6118-Dec-10 11:47 
Elegant solution, clearly explained. nice job!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.