Click here to Skip to main content
15,867,568 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.5K   524   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 
James Curran wrote:
Does this let me also convert a TypeC to a TypeB, or perhaps convert a TypeA into a TypeC?


Short answer - yes, but that depends on how much code you're willing to write for the IMappingProvider implementor. This is supposed to be a SIMPLE mapping utility so if it looked like we'd be writing thousands of lines of code to do the mapping we likely wouldn't have gone this route.

Longer answer - Theoretically, you could map any one type to any other. In the article, I've presented TypeA as an object which I have control over (i.e. I am able to modify the type and its members) and TypeB as a type which I have no control over e.g. a specific type defined by a third party that is expected as the argument to method. That said, it's entirely possible to map between two types that you have no control over by introducing a wrapper that inherits from IMappable which you could then pass through the concrete implementation of IMappingProvider.


James Curran wrote:
show the use case where the infastructure is useful.


I'll use the scenario for which I wrote this.

In our software, we have internal message types which report on business rule violations and process state, etc. We had to introduce a means to publish these messages to a third party tool. This tool is fugly. They expose a proxy which expects one of their own defined types as an argument. It's an overloaded method that expects different types for different functionality. This proxy never returns any information, it's meant to be a 'fire & forget' solution.

The types defined by the third party make very little logical sense. They have types which are literally collections of collections of collections, members and code repeated all over the show, nasty error handling and general mayhem. Even still, my job is not to criticize this code but to make sure our software plays nicely with it.

Rather than pollute our code with logic that this third party tool needs, I introduced this simple mapping layer which allows us to map to the specific types expected by the proxy. This way, when it comes to calling the proxy, I have a simple call to the mapper to return what I want when I need it, e.g. something like:

public void Publish(OurInternalType ourInternalType)
{
    ThirdPartyTypeA theirNastyType = mapper.Map<ThirdPartyTypeA>(ourInternalType);
    try
    {
       theirProxyInstance.ProxyCall(theirNastyType);
    }
    catch(Exception ex)
    {
       // blah, blah, blah
    }
}

public void Publish(OurInternalType ourInternalType)
{
    ThirdPartyTypeB theirNastyType = mapper.Map<ThirdPartyTypeB>(ourInternalType);
    try
    {
       theirProxyInstance.ProxyCall(theirNastyType);
    }
    catch(Exception ex)
    {
       // blah, blah, blah
    }
}

public void Publish(OurInternalType ourInternalType)
{
    ThirdPartyTypeC theirNastyType = mapper.Map<ThirdPartyTypeC>(ourInternalType);
    try
    {
       theirProxyInstance.ProxyCall(theirNastyType);
    }
    catch(Exception ex)
    {
       // blah, blah, blah
    }
}


I hope that clarifies the use case somewhat. Of couse, I can't publish our exact code but I'm hoping this is a 'near-enough' simile.


But fortunately we have the nanny-state politicians who can step in to protect us poor stupid consumers, most of whom would not know a JVM from a frozen chicken. Bruce Pierson
Because programming is an art, not a science. Marc Clifton
I gave up when I couldn't spell "egg". Justine Allen



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 

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.