I find myself frequently converting between formats, so I have come up with the following conversion framework. The converter interface surfaces a method to convert from a source type to a target type.
public interface IConverter<in TSource, out TTarget>
{
TTarget Convert(TSource source);
}
The factory is for producing concrete converters:
public interface IConverterFactory
{
IConverter<TSource, TTarget> GetConverter<TSource, TTarget>();
}
This next interface allows for the implementation of a generic converter that you can inject into a class and reuse for many conversions of different types (you can inject the factory instead but the code just looks cleaner with the generic converter):
public interface IGenericConverter
{
TTarget Convert<TSource, TTarget>(TSource source);
}
Here's a simple example to illustrate how these interfaces are implemented. First I will look at the factory as we can provide a pretty generic factory implementation.
public class ConverterFactory : IConverterFactory
{
private readonly Dictionary<Tuple<Type, Type>, Func<object>> _converters =
new Dictionary<Tuple<Type, Type>, Func<object>>();
public void RegisterConverter<TSource, TTarget>(Func<object> constructor)
{
_converters.Add(new Tuple<Type, Type>(typeof(TSource), typeof(TTarget)), constructor);
}
public IConverter<TSource, TTarget> GetConverter<TSource, TTarget>()
{
var constructor = _converters[new Tuple<Type, Type>(typeof (TSource), typeof (TTarget))];
return (IConverter<TSource, TTarget>) constructor();
}
}
The factory implementation introduces a dictionary to keep track of registered converters and the associated constructors for the converters. A registration method is added to allow registration of converters. And finally GetConverter
is implemented to look up the converter from the dictionary.
The generic converter can be implemented with a dependency on this factory like so:
public class GenericConverter : IGenericConverter
{
private readonly ConverterFactory _factory;
public GenericConverter(ConverterFactory factory)
{
_factory = factory;
}
public TTarget Convert<TSource, TTarget>(TSource source)
{
var converter = _factory.GetConverter<TSource, TTarget>();
return converter.Convert(source);
}
}
Inject a factory into the constructor and then Convert
can be called for any conversion and the class will get a converter from the factory, run the converter and return the result of the conversion.
Here are some models. Note the POCO models, absence of dependencies, completely different signatures and cleanliness.
public class Dog
{
public string Name { get; set; }
}
public class DogDescriptor
{
public string Name { get; set; }
}
public class Cat
{
public int Number { get; set; }
}
public class CatDescriptor
{
public int Number { get; set; }
}
So the plan is to convert from DogDescriptor
to Dog
and from CatDescriptor
to Cat
. The converters need to implement IConverter
.
public class DogConverter : IConverter<DogDescriptor, Dog>
{
public Dog Convert(DogDescriptor descriptor)
{
return new Dog {Name = descriptor.Name};
}
}
public class CatConverter : IConverter<CatDescriptor, Cat>
{
public Cat Convert(CatDescriptor descriptor)
{
return new Cat {Number = descriptor.Number};
}
}
Now we have all the bits and pieces we need to do a conversion:
var factory = new ConverterFactory();
// register converters
factory.RegisterConverter<DogDescriptor, Dog>(() => new DogConverter());
factory.RegisterConverter<CatDescriptor, Cat>(() => new CatConverter());
var converter = new GenericConverter(factory);
var dog = converter.Convert<DogDescriptor, Dog>(new DogDescriptor{Name = "Spot"});
var cat = converter.Convert<CatDescriptor, Cat>(new CatDescriptor{Number = 666});
Ideally you will use dependency injection to inject the generic converter into any classes that need to do conversions. Your dependency injection solution can then be configured to run the generic converter as a singleton.
Extending conversion support is fairly easy, assuming the two new models are introduced as Duck
and DuckDescriptor
. All the developer needs to do is implement a DuckDescriptor
-to-Duck
converter and then register the converter with the factory.
The factory has no dependencies and can easily be tested. The generic converter needs a mock factory injected for testing. Converters only have dependencies on the models they convert which makes testing converters extremely easy.
Questions
Am I reinventing the wheel? I couldn't find anything like it in the .NET framework but I also did not look very hard.
Many conversion implementations extend the model classes with
ToType
style methods. Does this approach offer any advantages?
While I can see the attraction of this approach, it requires an extension method for every conversion which starts getting ugly when you have to do vendor specific conversions of the same models. Using the factory solution, you would simply implement vendor specific factories and inject a generic converter constructed with the vendor specific factory.