Creating a Generic Type Converter for C#

Recently I was given the task of creating a type converter that would automatically convert objects between abstraction layers without using dedicated DTOs (data transfer objects). I was also given the limitation of not using dedicated constructors that accept another type and do the conversion manually.

The approach that was suggested to me used custom attributes to define which properties mapped to other properties. I’m not really a fan of custom attributes though — don’t get me wrong, they certainly have their place, but I find that they can make reading code more difficult if you don’t know what their functionality and how they are being manipulated through reflection.

My solution was thus:

  • accept an in object and an out object
  • iterate over all publicly exposed properties of the in object
    • generate a Dictionary of their name to their value
  • iterate over all publicly exposed properties of the out object
    • for any properties named the same, copy the value over

Here’s the code that accomplished this:

    public class TypeConverter
    {
        public TypeConverter()
        { }

        public T2 ConvertTypes<T1, T2>(T1 convertFrom, T2 convertTo)
        {
            var convertFromType = convertFrom.GetType();
            var propertyValues = new Dictionary<string, object>();
            var neededProperties = convertFromType.GetProperties();
            foreach (var property in neededProperties)
                propertyValues.Add(property.Name, property.GetValue(convertFrom, null));

            var convertToType = convertTo.GetType();
            var outputProperties = convertToType.GetProperties();
            foreach (var property in outputProperties)
                if (propertyValues.ContainsKey(property.Name))
                    property.SetValue(convertTo, propertyValues[property.Name], null);

            return convertTo;
        }
    }

This worked as advertised, but another question came up — what about conversions that are not 1:1? What if a property called Revision was a string in the object being converted from and an int in the object being converted to? Well, back to the drawing board.

This time I wanted to make sure that I am able to register non-standard conversions with the converter. When iterating over the properties of the outbound object, the property name and conversion types need to be checked to see if any custom conversions have been registered and use that conversion if one exists.

Here’s what I came up with:

    public class TypeConverter
    {
        public delegate dynamic ConvertProperty 
            (dynamic convertFrom, dynamic convertTo, string propertyName);
        private Dictionary<int, ConvertProperty> _customizations;

        public TypeConverter()
        {
            _customizations = new Dictionary<int, ConvertProperty>();
        }

        public void Register(Type convertFrom, Type convertTo, string propertyName, ConvertProperty convert)
        {
            int hash = new ConvertKey(convertFrom, convertTo, propertyName).GetHashCode();
            if (!_customizations.ContainsKey(hash))
                _customizations.Add(hash, convert);
        }

        public T2 ConvertTypes<T1, T2>(T1 convertFrom, T2 convertTo)
        {
            var convertFromType = convertFrom.GetType();
            var propertyValues = new Dictionary<string, object>();
            var neededProperties = convertFromType.GetProperties();
            foreach (var property in neededProperties)
                propertyValues.Add(property.Name, property.GetValue(convertFrom, null));

            var convertToType = convertTo.GetType();
            var outputProperties = convertToType.GetProperties();
            foreach (var property in outputProperties)
            {
                if (propertyValues.ContainsKey(property.Name))
                {
                    int hash = new ConvertKey(convertFromType, convertToType, property.Name)
                        .GetHashCode();
                    if (_customizations.ContainsKey(hash))
                        property.SetValue(convertTo, _customizations[hash](convertFrom, convertTo, property.Name), null);
                    else
                        property.SetValue(convertTo, propertyValues[property.Name], null);
                }
            }

            return convertTo;
        }

        private class ConvertKey
        {
            private Type _convertFrom;
            private Type _convertTo;
            private string _propertyName;

            public ConvertKey(Type convertFrom, Type convertTo, string propertyName)
            {
                _convertFrom = convertFrom;
                _convertTo = convertTo;
                _propertyName = propertyName;
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    return _convertFrom.GetHashCode() * _convertTo.GetHashCode() 
                        * _propertyName.GetHashCode();
                }
            }
        }
    }

As you can see, there is now a Dictionary internal to the class which holds an int key and a ConvertProperty delegate — which takes a from type, to type, and property name.

A Register method has been added to register new custom conversions. It calculates a hash to use as the Dictionary key, and if that key does not already exist, adds the delegate to complete the key value pair.

That hash key is a simple unchecked calculation of the hash codes of the two types multiplied with the hash code of the property name.

The last addition is that, as mentioned, when iterating over outbound properties a hash is calculated and checked against the dictionary and if needed, the custom conversion is run.

Here is a test in action:

        public void StraightConvert()
        {
            var convertFrom = new SampleClass1() { Num = 1 };
            var result = _converter.ConvertTypes(convertFrom, new SampleClass2());
            Assert.AreEqual(result.Num, convertFrom.Num);
        }
        public void ConvertWithRegister()
        {
            _converter.Register(typeof(SampleClass1), typeof(SampleClass2), "Num", (cF, cT, pN) =>
            {
                var cFType = cF.GetType();
                return (int)cFType.GetProperty(pN).GetValue(cF, null) * 10; 
            });

            var convertFrom = new SampleClass1() { Num = 1 };
            var result = _converter.ConvertTypes(convertFrom, new SampleClass2());
            Assert.AreEqual(convertFrom.Num * 10, result.Num);
        }

But the capabilities of this are greater than just multiplying by 10. It would be very easy to have the input type be a list of dates, from which the max date is pulled, as an example. Any type of executable code you need can be registered with the converter.

By far, this is one of the most fun pieces of code I’ve written in C# to date.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s