Reputation: 3694
I'd like to implement a basic mapping system similar to AutoMapper, but all explicit mappings, no convention-based mappings.
To do this I've written a class that is supposed to maintain a register of "mapping" functions and look them up on demand in order to map one type to another. The usage I'm envisaging is like this:
Somewhere in startup:
Mapper.Register<TypeA, TypeB>(typeA =>
{
var typeB = new TypeB()
{
Property1 = typeA.Property1
};
return typeB;
}
And then when I want to perform the mapping...
TypeA typeA = new TypeA();
TypeB typeB = Mapper.Map<TypeA, TypeB>(typeA);
Currently I've used a Dictionary<Tuple<Type, Type>, Delegate>
to store this mapping register, but it's not working as I had hoped...
public static class Mapper
{
private readonly static Dictionary<Tuple<Type, Type>, Delegate> Mappings = new Dictionary<Tuple<Type, Type>, Delegate>();
public static void Register<TSource, TDestination>(Func<TSource, TDestination> mappingFunction)
where TSource : class
where TDestination : class
{
Delegate mappingFunc;
if (Mappings.TryGetValue(new Tuple<Type, Type>(typeof (TSource), typeof (TDestination)), out mappingFunc))
Mappings[new Tuple<Type, Type>(typeof (TSource), typeof (TDestination))] = mappingFunction;
else
Mappings.Add(new Tuple<Type, Type>(typeof (TSource), typeof (TDestination)), mappingFunction);
}
public static TDestination Map<TSource, TDestination>(TSource src)
where TSource : class
where TDestination : class
{
Delegate mappingFunc;
if (!Mappings.TryGetValue(new Tuple<Type, Type>(typeof (TSource), typeof (TDestination)), out mappingFunc))
throw new Exception("Invalid mapping: no mapping found for requested types.");
var func = mappingFunc as Func<TSource, TDestination>;
if (func == null)
throw new Exception("Invalid mapping: no mapping found for requested types.");
return func.Invoke(src);
}
}
When this code is used, registering the mappings works fine, but retrieving them from the dictionary fails, and I think it's because of the third line in the Map
method:
Mappings.TryGetValue(new Tuple<Type, Type>(typeof (TSource), typeof (TDestination)), out mappingFunc)
This line always fails to pass the test, and I think if I understand correctly, it's because Tuple is a reference type, so a new instance of Tuple<Type, Type>
, regardless of what Item1
and Item2
are, will never match any key in the Dictionary
.
So, Dictionary<Tuple<Type, Type>, Delegate>
is not appropriate for storing this mapping register. In that case, what data type is?
Upvotes: 3
Views: 83
Reputation: 7686
When I try to run your above code, I get the result I expect:
Mapper.Register<string, Regex>(s => new Regex("not using the given string"));
Mapper.Register<string, Regex>(s => new Regex(s));
var regex = Mapper.Map<string, Regex>(@"\w*");
// regex is now the Regex object instantiated with @"\w*"
In other words, your code seems to be functioning correctly.
This line always fails to pass the test, and I think if I understand correctly, it's because Tuple is a reference type, so a new instance of
Tuple<Type, Type>
, regardless of whatItem1
andItem2
are, will never match any key in theDictionary
.
Actually, the implementation of Tuple
does support what you are trying to do. The Dictionary
uses GetHashCode
and Equals
methods under the hood to make lookups. The Equals
method usually checks reference equality for objects, but the Tuple
source code shows that it is specifically programmed to use structural equality:
public override Boolean Equals(Object obj) {
return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
}
Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
if (other == null) return false;
Tuple<T1, T2> objTuple = other as Tuple<T1, T2>;
if (objTuple == null) {
return false;
}
return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2);
}
A Dictionary
is the right way to go for this use case, as an arbitrary number of registration can be made and we would like to ensure fast lookup. I think, however, that I would probably still create my own type TypeMapping
to use for the Dictionary
instead of Tuple<Type, Type>
, because I think tuples do not express the intend/usage. Just remember to override GetHashCode
and Equals
so it functions correctly with Dictionary
like so:
public class TypeMapping : IStructuralEquatable
{
public Type From { get; private set; }
public Type To { get; private set; }
public TypeMapping (Type from, Type to)
{
From = from;
To = to;
}
public override int GetHashCode()
{
return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
}
int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
{
var hash = 17;
unchecked
{
hash = hash * 31 + From.GetHashCode();
hash = hash * 31 + To.GetHashCode();
}
return hash;
}
public override bool Equals(Object obj) {
return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);
}
bool IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
if (other == null) return false;
var otherMapping = other as TypeMapping;
if (otherMapping == null) return false;
return comparer.Equals(From, otherMapping.From) && comparer.Equals(To, otherMapping.To);
}
}
And then you would have your Dictionary
in the Mapping class look like this (with corresponding changes to Registration
and Map
methods):
private readonly static Dictionary<TypeMapping, Delegate> Mappings = new Dictionary<TypeMapping, Delegate>();
Upvotes: 3