Click here to Skip to main content
15,887,027 members
Articles / Programming Languages / C#
Article

Lightweight Object to Object Mapper

Rate me:
Please Sign up or sign in to vote.
4.79/5 (12 votes)
17 Sep 2008CPOL8 min read 55.7K   601   37   10
An object to help map from one object to another
Image 1

Introduction

This article includes an object I put together that helps alleviate a common problem, mapping a particular object to a separate object. Common scenarios include:

  • Using DTO objects for remote calls
  • Presentation Model objects

It is not by any means any attempt at an OR/M. There are already lots of great OR/Ms available to use and I'm not a fan of NIH mentality. This object serves a different purpose.

Background

I have been using a Domain Model pattern using DDD with nHibernate for most apps and I'm not going to be changing that any time soon. I like having the rules and behaviour inside the domain objects and being able to thoroughly unit test them without having the database and every service under the sun running.

One thing I have found that increases development friction is the process of mapping objects against other objects. You tend to end up with code that has your domain objects setting properties onto your DTO objects with usually the same named properties or very similar, for example:

C#
// customer created elsewhere
CustomerDTO customerDTO = new CustomerDTO();
customerDTO.DOB = customer.DOB;

Granted the new initializers syntax has made this a bit more bearable:

C#
CustomerDTO customerDTO = new CustomerDTO()
{
    DOB = customer.DOB
};

However this is still more than I would like to have to write.

Requirements of Use

  • No need to have to change any of my objects to be mapped, i.e. adding attributes, I like to keep the domain objects clean from noise like this.
  • No need to have a massive XML file with all the mapping information, for two main reasons:
    1. XML is too verbose, I may as well stick to the current way rather than go down the route of having to write streams of XML.
    2. Type safety
  • Not having to refer to properties of objects by string. I don't want my mapper to stand in the way of being able to refactor my code as I go along by throwing a runtime exception when I decide to rename a property.
  • A nice API to work with some of the best APIs for things like this are implemented in a fluent interface style, think Rhino Mocks and StructureMap.
  • Convention over configuration for mapping, if the properties are of the same type and are the same name then just map them. I want to cut down on the amount of code I have to write to tell the mapper which properties to map, if it can use a convention like this one it saves writing lots of superfluous code.
  • Provide a default no arg constructor for my objects, this is fair enough. nHibernate expects the same and it doesn't interfere too much with the domain objects.
  • Use of properties for mapping, this seems fine. I can't think of an occasion where I have needed to map to fields instead, so until one arises properties will do.

The Implementation, BDD Style

As per my usual approach, I drove out the design using BDD (TDD with emphasis on behaviour), I find this very useful for objects like this because you can then concentrate on getting the API for the caller right before going down the route of creating all the code from the inside out and finding the API cumbersome to use.

Null Checking

I start simple, my first test looks like this:

C#
[TestFixture]
public class When_trying_to_apply_mappings_with_nulls : 
	simple_object_mapper_specification
{
    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void Should_throw_null_arg_exception_if_source_is_null()
    {
        SimpleObjectMapper<Car, CarDto> sut = createSUT();
        sut.Apply((Car)null);
    }
}

simple_object_mapper_specification is just the test base class that holds the createSUT [System Under Test] method and some of the shared fields. Here we make sure that by passing a null into the Apply method, we throw the appropriate exception.

And to make this pass:

C#
public class SimpleObjectMapper<Source, Destination>
        where Source : new()
        where Destination : new()
{
    public virtual Destination Apply(Source source)
    {
        if (source == null)
            throw new ArgumentNullException
                  (&quot;source&quot;, &quot;source cannot be null&quot;);

        return null;
    }
}

Testing Conventions

We start to flesh out the bare minimum, By making use of Generics, we get strong typing and don't have to go down the casting route. We now want to check our conventions about the properties are working, now we need to have some objects to map against, so I create the following in the test project:

C#
public class Car
{
    public string Model { get; set; }
    public string Make { get; set; }
    public string Color { get; set; }
    public Engine Engine { get; set; }
}

public class CarDto
{
    public string FullDescription { get; set; }
    public string Color { get; set; }
    public EngineDto Engine { get; set; }
}

public class Engine
{
    public int MaxRPM { get; set; }
    public int Capacity { get; set; }
}

public class EngineDto
{
    public int Capacity { get; set; }
}

Note that we have some mismatches of properties and one that matches the convention.

C#
[TestFixture]
public class When_simple_object_mapper_is_mapping_properties_by_convention : 
	simple_object_mapper_specification
{
    [Test]
    public void Should_set_values_where_name_matches_and_same_type()
    {
        SimpleObjectMapper<Car, CarDto> sut = createSUT();
        carDto = sut.Apply(car);

        Assert.That(carDto.Color, Is.EqualTo(&quot;Red&quot;));
    }
}

As we have seen above, only the Color property matches the convention specified before [in most real world apps, it tends to be most properties that meet the convention], so we want to get this to pass:

C#
public virtual Destination Apply(Source source)
{
    var destination = new Destination();

    if (source == null)
        throw new ArgumentNullException
			(&quot;source&quot;, &quot;source cannot be null&quot;);

    var sourceProps = typeof(Source).GetProperties();
    var destinationProps = typeof(Destination).GetProperties();

    foreach (var sourceProp in sourceProps)
        foreach (var destinationProp in destinationProps)
            if((sourceProp.Name.Equals
                 (destinationProp.Name, StringComparison.InvariantCultureIgnoreCase)
                && (sourceProp.PropertyType.Equals(destinationProp.PropertyType))))
            {
                var sourceVal = sourceProp.GetValue(source, null);
                destinationProp.SetValue(destination, sourceVal, null);
            }

    return destination;
}

Okay, so we have added some reflection here to get properties of the source and destination types, we then enumerate over each and check for any matches, this gives us the passed test, now to refactor:

C#
public class PropertyMatch
{
    public PropertyInfo SourceProperty;
    public PropertyInfo DestinationProperty;

    public PropertyMatch(PropertyInfo sourceProp, PropertyInfo destinationProp)
    {
        SourceProperty = sourceProp;
        DestinationProperty = destinationProp;
    }
}

The reason for this class will become clear after the next code sample:

C#
public class SimpleObjectMapper<Source, Destination>
        where Source : new()
        where Destination : new()
    {
        protected IList<PropertyMatch> propertyMatches = new List<PropertyMatch>();

        public SimpleObjectMapper()
        {
            var sourceProps = typeof(Source).GetProperties();
            var destinationProps = typeof(Destination).GetProperties();

            foreach (var sourceProp in sourceProps)
                foreach (var destinationProp in destinationProps)
                    if((sourceProp.Name.Equals(destinationProp.Name, 
				StringComparison.InvariantCultureIgnoreCase)
                        && (sourceProp.PropertyType.Equals
					(destinationProp.PropertyType))))
                        propertyMatches.Add(new PropertyMatch
					(sourceProp, destinationProp));
        }

        public virtual Destination Apply(Source source)
        {
            var destination = new Destination();

            if (source == null)
                throw new ArgumentNullException
			(&quot;source&quot;, &quot;source cannot be null&quot;);

            foreach (var propertyMatch in propertyMatches)
            {
                var sourceVal = propertyMatch.SourceProperty.GetValue(source, null);
                propertyMatch.DestinationProperty.SetValue(destination, sourceVal, null);
            }

            return destination;
        }
    }

Reflecting over types isn't the most efficient task you can do, therefore we can improve the performance for multiple calls to our Apply method by performing the reflect when the SimpleObjectMapper is created. That way it gets done once per instance, hence the need for the PropertyMatch class to create the association between the two PropertyInfo objects.

Note that this code has changed slightly in that I am now taking advantage of the DynamicAccessor object supplied by C# and now use IsAssignableFrom as opposed to Equals. See comment.

Testing Awkward Mappings

The convention based mapping we have set up is pretty good and should get us most of the way there. However there are times where you need more control over the setting of properties that don't follow the convention. Let's get a test written to spec this out:

C#
[TestFixture]
public class When_simple_object_mapper_is_mapping_properties_explicitly : 
	simple_object_mapper_specification
{

    [Test]
    public void Should_map_any_explicit_mappings_specified()
    {
        SimpleObjectMapper<Car, CarDto> sut = createSUT();
        carDto = sut.Explicit((model,dto) => dto.FullDescription = model.Make + 
				&quot; - &quot; + model.Model)
                    .Apply(car);

        Assert.That(carDto.FullDescription, Is.EqualTo(&quot;Audi - A4&quot;));
    }
}

The CarDTO.FullDescription property is a prime example of this, whereby the property may contain values from other properties on the domain object. The above API feels good, by utilizing a Lambda statement we have kept the code down to a minimum but have the power to assign pretty much any value to a specific property on the DTO. Another benefit is compile time checking.

The other thing you may have picked up on is the fluent interface API, you can already start to see how readable it would be if you needed to specify a list of explicit mappings. Now we need to get this to pass:

C#
public class SimpleObjectMapper<Source, Destination>
    where Source : new()
    where Destination : new()
{
    protected IList<PropertyMatch> propertyMatches = new List<PropertyMatch>();
    protected IList<Action<Source, Destination>> explicitActions = 
		new List<Action<Source, Destination>>();

    public SimpleObjectMapper()
    {
        var sourceProps = typeof(Source).GetProperties();
        var destinationProps = typeof(Destination).GetProperties();

        foreach (var sourceProp in sourceProps)
            foreach (var destinationProp in destinationProps)
                if((sourceProp.Name.Equals(destinationProp.Name, 
				StringComparison.InvariantCultureIgnoreCase)
                    && (sourceProp.PropertyType.Equals(destinationProp.PropertyType))))
                    propertyMatches.Add(new PropertyMatch(sourceProp, destinationProp));
    }

    public virtual SimpleObjectMapper<Source, Destination> Explicit
		(Action<Source, Destination> explicitAction)
    {
        explicitActions.Add(explicitAction);
        return this;
    }

    public virtual Destination Apply(Source source)
    {
        var destination = new Destination();

        if (source == null)
            throw new ArgumentNullException
			(&quot;source&quot;, &quot;source cannot be null&quot;);

        foreach (var propertyMatch in propertyMatches)
        {
            var sourceVal = propertyMatch.SourceProperty.GetValue(source, null);
            propertyMatch.DestinationProperty.SetValue(destination, sourceVal, null);
        }

        foreach (var action in explicitActions)
            action(source, destination);

        return destination;
    }
}

The implementation of this becomes very simple thanks to the power of Lambdas, we simply keep a list of Action<Source, Destination> objects around and add to them for every explicit call made (it would be nicer to use an Expression to be able to do checks against the lambda but you can't perform assignments for an Expression. If someone knows a way, let me know!). Next when Apply is called, we enumerate over each of the actions and pass the objects in to be worked on.

Nested Objects

So far we have only used properties that represent a straight forward mapping. However what about the Engine property that exists on both domain and DTO? It doesn't get picked up by the convention because they are different types as they should be, but how do we map from the domain to the DTO... well we already have a SimpleObjectMapper at our disposal and a way of specifying explicit mappings so why not just leverage them!

C#
[TestFixture]
public class When_simple_object_mapper_is_mapping_a_deep_property : 
	simple_object_mapper_specification
{
    [Test]
    public void Should_use_specified_object_mapper_in_explicit_to_perform_mapping()
    {
        SimpleObjectMapper<Engine, EngineDto> engineMapper = 
			new SimpleObjectMapper<Engine, EngineDto>();
        SimpleObjectMapper<Car, CarDto> sut = createSUT();
        carDto = sut.Explicit((model,dto) => dto.Engine = 
			engineMapper.Apply(model.Engine))
                    .Apply(car);

        Assert.That(carDto.Engine.Capacity, Is.EqualTo(1998));
    }
}

This demonstrates the power gained from using Lambda statements. We haven't needed to make any changes to the SimpleObjectMapper, instead we have introduced a new SimpleObjectMapper instance that knows how to map from Engine to EngineDto and just inform the main car mapper to use it when mapping the Engine property, pretty cool, eh... we can now use this new mapper in isolation at certain times and combined with the main car mapper at other times with no duplication of mapping details.

Collection Mappings

It's often the case when performing mappings that instead of working with a single domain object, you may be working with a collection. In this case, you want to be able to enumerate over this collection and get a collection of mapped objects back. Let's write a test to enable the SimpleObjectMapper to support this:

C#
[TestFixture]
public class When_simple_object_mapper_is_asked_to_map_object_collections : 
	simple_object_mapper_specification
{
    [Test]
    public void Should_be_able_to_map_properties_for_each_source()
    {
        SimpleObjectMapper<Car, CarDto> sut = createSUT();
        Car car2 = new Car()
        {
            Color = &quot;Blue&quot;
        };

        IEnumerable<Car> cars = new List<Car>() { car, car2 };
        IEnumerable<CarDto> results = sut.Apply(cars);

        Assert.That(results.Any(item => item.Color == car.Color));
        Assert.That(results.Any(item => item.Color == car2.Color));
    }
} 

One thing to note is that instead of a new method I have chosen just to overload the Apply method, this is just personal preference. Moreover, I have chosen to accept IEnumerable for both. This gives us the lowest denominator so the collection passed could be any type that implemented IEnumerable (every FCL collection I think).

C#
public virtual IEnumerable<Destination> Apply(IEnumerable<Source> sources)
{
    return sources.Select<Source, Destination>(source => Apply(source));
}

Easy, once again Lambda statements to the rescue to save me from having to write a foreach with a yield return. I can instead leverage the Select extension method to return a custom IEnumerable result. Inside the statement, I simply forward the item to the Apply method.

Managing Mappers

We now have a way to define a SimpleObjectMapper to be able to map two objects. We can re-use this instance over and over by using the Apply method. What would be nice would be to have somewhere to keep these instances to be able to retrieve the appropriate mapper when required, this is why I created the SimpleObjectMapperContainer.

It functions similar to an IoC container (Windsor, StructureMap, etc.) in that you register the instances with it and then are able to retrieve the instance further down the line, it is a static class with all methods being threadsafe.

Typical Usage Scenario

For this example, I'm going to use a Web application example:

C#
// ... inside Global.asax
protected void Application_Start()
{
    BootStrapper.RegisterMappers();
}

public static class BootStrapper
{
    public static void RegisterMappers()
    {
        SimpleObjectMapperContainer.RegisterMapper
		(new SimpleObjectMapper<Administrator, AdministratorDTO>());
        SimpleObjectMapperContainer.RegisterMapper
		(new SimpleObjectMapper<Location, LocationDTO>());

        SimpleObjectMapperContainer.RegisterMapper
	(new SimpleObjectMapper<Staff, StaffDTO>()
	.Explicit((staff, staffDTO) => staffDTO.LocationId = staff.Location.Id)
	.Explicit((staff, staffDTO) => staffDTO.LocationDescription = 
		staff.Location.Value)
	.Explicit((staff, staffDTO) => staffDTO.Photo.Data = staff.Photo));
    }
}

At application start up, all we do is register our SimpleObjectMapper instances with the SimpleObjectMapperContainer and that's it.

C#
var adminMapper = SimpleObjectMapper.RetrieveMapper<Administrator, AdministratorDTO>();
var dto = adminMapper.Apply(admin);

Then to get hold of the instance we simply use the right type parameters.

Going Forward

Feel free to make adjustments to the code. What I would ask is if you please let me know, I could introduce the changes and update the source here.
Below are some ideas:

  • Mapping to fields if needed
  • More control over convention? possibly injection of a convention pattern
  • Using Expression for explicit mappings to do some checking (not supported at present)

History

  • 1.0.0.0 Initial Version - 4th September, 2008
  • 1.0.1.0 Amendments after feedback - 16th September, 2008
    • Added DynamicAccessor to use instead of standard reflection for getting and setting property values
    • Changed type check for convention to use IsAssignableFrom rather than Equals
    • Introduced new SimpleObjectMapperContainer to help with management of SimpleObjectMapper instances

License

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


Written By
Software Developer
United Kingdom United Kingdom
Have worked in web/software developement since leaving college in 2002, initially for a software house now for a private bank in the IOM, became MCAD certified in 2006 and now studying to get MCSD certified.

I'm very interested in using design patterns, good design practices & principles to create well written maintainable software

Comments and Discussions

 
QuestionGood article Pin
Sandeep Pote12-Mar-13 20:42
Sandeep Pote12-Mar-13 20:42 
GeneralVery Interesting - how about objects with collections Pin
waltbp31-Aug-09 10:49
waltbp31-Aug-09 10:49 
GeneralEnumerations and sub objects Pin
TheGreatAndPowerfulOz25-Aug-09 10:14
TheGreatAndPowerfulOz25-Aug-09 10:14 
GeneralUse this instead... Pin
stavinski19-Feb-09 5:52
stavinski19-Feb-09 5:52 
QuestionOver Engineering - Why not just use a simple Serializable Dictionary as a DTO for all objects? Pin
Tawani Anyangwe17-Sep-08 10:07
Tawani Anyangwe17-Sep-08 10:07 
AnswerRe: Over Engineering - Why not just use a simple Serializable Dictionary as a DTO for all objects? [modified] Pin
stavinski17-Sep-08 23:02
stavinski17-Sep-08 23:02 
GeneralFormatting bug Pin
Todd Smith17-Sep-08 9:39
Todd Smith17-Sep-08 9:39 
GeneralRe: Formatting bug Pin
stavinski17-Sep-08 22:30
stavinski17-Sep-08 22:30 
GeneralBrilliant Pin
seesharper9-Sep-08 4:09
seesharper9-Sep-08 4:09 
Hi Stavinski!

Your solution here is very clean and well explained. You got my five for this!

I do however have some suggestions for you.

The GetValue and SetValue methods used for copying the values are going have a great impact on performance.

Let me hook you up with some code that fixes that

/// <summary>
   /// Provides a simple interface to late bind a class.
   /// </summary>
   /// <remarks>The first time you attempt to get or set a property, it will dynamically generate the get and/or set
   /// methods and cache them internally.  Subsequent gets uses the dynamic methods without having to query the type's
   /// meta data.</remarks>
   public class LateBinder
   {
       #region Private Fields
       private Type _type;
       private Dictionary<string,> _propertyGetters;
       private Dictionary<string,> _propertySetters;
       private Dictionary<string,> _fieldGetters;
       private Dictionary<string,> _fieldSetters;
       #endregion

       #region Constructors




       internal LateBinder(Type type)
       {
           _type = type;
           _propertyGetters = new Dictionary<string,>();
           _propertySetters = new Dictionary<string,>();
           _fieldGetters = new Dictionary<string,>();
           _fieldSetters = new Dictionary<string,>();
       }
       #endregion

       #region Public Accessors


       /// <summary>
       /// Gets or Sets the supplied property on the supplied target
       /// </summary>
       public object this[object target, string propertyName]
       {
           get
           {
               ValidatePropertyGetter(propertyName);
               return _propertyGetters[propertyName](target);
           }
           set
           {
               ValidatePropertySetter(propertyName);
               _propertySetters[propertyName](target, value);
           }
       }

       public void SetPropertyValue(object target, string propertyName, object value)
       {
           ValidatePropertySetter(propertyName);
           _propertySetters[propertyName](target, value);
       }

       public object GetPropertyValue(object target, string propertyName)
       {
           ValidatePropertyGetter(propertyName);
           return _propertyGetters[propertyName](target);
       }


       public void SetFieldValue(object target, string fieldName, object value)
       {
           ValidateFieldSetter(fieldName);
           _fieldSetters[fieldName](target, value);
       }

       public object GetFieldValue(object target, string fieldName)
       {
           ValidateFieldGetter(fieldName);
           return _fieldGetters[fieldName](target);
       }

       #endregion

       #region Private Helpers


       private void ValidateFieldSetter(string fieldName)
       {
           if (!_fieldSetters.ContainsKey(fieldName))
           {
               FieldInfo fieldInfo = _type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);

               if (fieldInfo == null)
                   throw new ArgumentOutOfRangeException(fieldName, "Unable to find fieldname");
               _fieldSetters.Add(fieldName, DynamicMethodCompiler.CreateSetHandler(_type, fieldInfo));
           }
       }

       private void ValidatePropertySetter(string propertyName)
       {
           if (!_propertySetters.ContainsKey(propertyName))
           {
               _propertySetters.Add(propertyName, DynamicMethodCompiler.CreateSetHandler(_type, _type.GetProperty(propertyName)));
           }
       }

       private void ValidatePropertyGetter(string propertyName)
       {
           if (!_propertyGetters.ContainsKey(propertyName))
           {
               _propertyGetters.Add(propertyName, DynamicMethodCompiler.CreateGetHandler(_type, _type.GetProperty(propertyName)));
           }
       }
       private void ValidateFieldGetter(string fieldName)
       {
           if (!_fieldGetters.ContainsKey(fieldName))
           {
               FieldInfo fieldInfo = _type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
               if (fieldInfo == null)
                   throw new ArgumentOutOfRangeException(fieldName, "Unable to find fieldname");
               _fieldGetters.Add(fieldName, DynamicMethodCompiler.CreateGetHandler(_type, fieldInfo));
           }
       }
       #endregion

       #region Contained Classes
       internal delegate object GetHandler(object source);
       internal delegate void SetHandler(object source, object value);
       internal delegate object InstantiateObjectHandler();

       /// <summary>
       /// provides helper functions for late binding a class
       /// </summary>
       /// <remarks>
       /// Class found here:
       /// http://www.codeproject.com/useritems/Dynamic_Code_Generation.asp
       /// </remarks>
       internal sealed class DynamicMethodCompiler
       {
           // DynamicMethodCompiler
           private DynamicMethodCompiler() { }


           internal static InstantiateObjectHandler CreateInstantiateObjectHandler(Type type)
           {
               ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null);
               if (constructorInfo == null)
               {
                   throw new ApplicationException(string.Format("The type {0} must declare an empty constructor (the constructor may be private, internal, protected, protected internal, or public).", type));
               }

               DynamicMethod dynamicMethod = new DynamicMethod("InstantiateObject", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(object), null, type, true);
               ILGenerator generator = dynamicMethod.GetILGenerator();
               generator.Emit(OpCodes.Newobj, constructorInfo);
               generator.Emit(OpCodes.Ret);
               return (InstantiateObjectHandler)dynamicMethod.CreateDelegate(typeof(InstantiateObjectHandler));
           }

           // CreateGetDelegate
           internal static GetHandler CreateGetHandler(Type type, PropertyInfo propertyInfo)
           {
               MethodInfo getMethodInfo = propertyInfo.GetGetMethod(true);
               DynamicMethod dynamicGet = CreateGetDynamicMethod(type);
               ILGenerator getGenerator = dynamicGet.GetILGenerator();

               getGenerator.Emit(OpCodes.Ldarg_0);
               getGenerator.Emit(OpCodes.Call, getMethodInfo);
               BoxIfNeeded(getMethodInfo.ReturnType, getGenerator);
               getGenerator.Emit(OpCodes.Ret);

               return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler));
           }

           // CreateGetDelegate
           internal static GetHandler CreateGetHandler(Type type, FieldInfo Season)
           {
               DynamicMethod dynamicGet = CreateGetDynamicMethod(type);
               ILGenerator getGenerator = dynamicGet.GetILGenerator();

               getGenerator.Emit(OpCodes.Ldarg_0);
               getGenerator.Emit(OpCodes.Ldfld, Season);
               BoxIfNeeded(Season.FieldType, getGenerator);
               getGenerator.Emit(OpCodes.Ret);

               return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler));
           }

           // CreateSetDelegate
           internal static SetHandler CreateSetHandler(Type type, PropertyInfo propertyInfo)
           {
               MethodInfo setMethodInfo = propertyInfo.GetSetMethod(true);
               DynamicMethod dynamicSet = CreateSetDynamicMethod(type);
               ILGenerator setGenerator = dynamicSet.GetILGenerator();

               setGenerator.Emit(OpCodes.Ldarg_0);
               setGenerator.Emit(OpCodes.Ldarg_1);
               UnboxIfNeeded(setMethodInfo.GetParameters()[0].ParameterType, setGenerator);
               setGenerator.Emit(OpCodes.Call, setMethodInfo);
               setGenerator.Emit(OpCodes.Ret);


               return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler));
           }

           // CreateSetDelegate
           internal static SetHandler CreateSetHandler(Type type, FieldInfo Season)
           {
               DynamicMethod dynamicSet = CreateSetDynamicMethod(type);
               ILGenerator setGenerator = dynamicSet.GetILGenerator();

               setGenerator.Emit(OpCodes.Ldarg_0);
               setGenerator.Emit(OpCodes.Ldarg_1);
               UnboxIfNeeded(Season.FieldType, setGenerator);
               setGenerator.Emit(OpCodes.Stfld, Season);
               setGenerator.Emit(OpCodes.Ret);

               return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler));
           }

           // CreateGetDynamicMethod
           private static DynamicMethod CreateGetDynamicMethod(Type type)
           {
               return new DynamicMethod("DynamicGet", typeof(object), new Type[] { typeof(object) }, type, true);
           }

           // CreateSetDynamicMethod
           private static DynamicMethod CreateSetDynamicMethod(Type type)
           {
               return new DynamicMethod("DynamicSet", typeof(void), new Type[] { typeof(object), typeof(object) }, type, true);
           }

           // BoxIfNeeded
           private static void BoxIfNeeded(Type type, ILGenerator generator)
           {
               if (type.IsValueType)
               {
                   generator.Emit(OpCodes.Box, type);
               }
           }

           // UnboxIfNeeded
           private static void UnboxIfNeeded(Type type, ILGenerator generator)
           {
               if (type.IsValueType)
               {
                   generator.Emit(OpCodes.Unbox_Any, type);
               }
           }
       }
       #endregion
   }



I can guarantee that this will speed things up.

The class actually emits the code needed to access the properties and then caches it.

This way you only perform the reflection once when it comes to setting and getting the values.

Another thing is that you maybe want to keep a per type property map so you don't have to resort to reflection everytime
a SimpleObjectMapper is created.

One last comment Smile | :)

When checking the source type against the destination type you should consider to do typeof(destination).IsAssignableFrom(source) instead of the "equals" requirement.

Regards

//seesharper
GeneralRe: Brilliant Pin
stavinski11-Sep-08 5:07
stavinski11-Sep-08 5:07 

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.