20110506

Cheap type conversion for very similar objects

Ever get stuck between two API and have to keep converting between two almost identical sets of data objects because neither API can be modded at the moment?  If you can afford some performance penalty, this is a quick way to get fields and properties with the same names synced.

First you add some implicit type operators:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace typeoverridedemostrater
{
    class Class2
    {
        public string string1;
        public string string2;
        public string string3;
 
        public static implicit operator Class2(Class1 initialData)
        {
            return new SuperConverter<Class1Class2>().Convert(initialData);
        }
 
        public static implicit operator Class2(Class3 initialData)
        {
            return new SuperConverter<Class3Class2>().Convert(initialData);
        }
    }
}

After  you call the converter, you can add whatever specific conversions you need.

(EDIT - as Brooke pointed out, the real version of this was an extension to an existing class, not a class with an internal operator the code above was from the demo project I bashed out to explain the concept to another dev)

Then you use some reflection and linq to do the assignments in a separate class that you can re-use in as many type converters as you like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace typeoverridedemostrater
{
    class SuperConverter where T2 : new()
    {
        public T2 Convert(T1 i)
        {
            var temp = new T2();
 
            typeof(T1).GetProperties().ToList().Where(a => a.CanWrite).ToList().
                ForEach(b =>
                {
                    var z = temp.GetType().GetProperty(b.Name);
                    if (z != null) z.SetValue(temp, b.GetValue(i, null), null);
                });
            
            typeof(T1).GetFields().ToList().ToList().
                ForEach(b =>
                {
                    var z = temp.GetType().GetField(b.Name);
                    if (z != null) z.SetValue(temp, b.GetValue(i));
                });
 
            return temp;
        }
    }
}


Obviously this only works in some cases, and if your types do not match precisely you would have to make it a little more complex by checking for compatible types, or handling failure more gracefully, but it can save a ton of time in some cases.

6 comments:

  1. Your solution doesn't abide by the rules stated in your problem. "neither API can be modded at the moment" yet you're adding implicit operators to the API, that counts as "modded".

    There are excellent tools in existence to do this already, which are likely much faster due to the fact that they generate the mapping code in MSIL when you create the map, then just call it when mapping objects. Using reflection to do this is going to be far slower. AutoMapper and ValueInjector are two examples of Libraries to do this.

    This seems like an example how not to use operator overloading. If anything an extension method would be best.

    ReplyDelete
  2. The right answer is to give them a common base or interface, second best would be MSIL or dynamic tricks.

    This solution has a significant perf penalty and is a questionable use of operator overloading if you are a stickler for that on a temporary hack, but it does not mod the signature outside the WCF or require any of the players to add a new reference to a third party tool that would require approval.

    The actual use of this was as an extension and I will edit accordingly, thanks for pointing this out.

    ReplyDelete
  3. I should probably also point out if it is not clear that it assumes a lot about the objects similarity and cannot do readonly collection property item adds, indexed properties and a few other things at all. It is not high performance and under load will get worse. Pretty much don't do this anywhere very important and don't do anything remotely like it except as a temporary measure.

    ReplyDelete
  4. Giving them a common base or interface assumes you can modify both apis, and if that's the case it's an entirely different problem space. If the goal is either A) to work with two disperate apis which you don't control or B) to eliminate coupling where it shouldn't exist, then the mapper pattern is the correct approach.

    ReplyDelete
  5. This was the sort of case of where the dependency should exist, but somebody overlooked it and we needed to get something up for a demo.

    There are definitely some nice things you can do in this area if you actually have a case where you need this long-term and you need top performance. this case had neither requirement and this has the advantage of not requiring a pass w/the full project team to get approval for more 3rd party code.

    ReplyDelete
  6. check out the next couple of posts to see what the more advanced methods might look like

    ReplyDelete

Firefox Feedly RSS option

If you use Firefox with a RSS button and want the default RSS page to offer a Feedly option here is what you need to do: go to the about:c...