Last active
August 29, 2015 14:17
-
-
Save GeirGrusom/bb91456e47a62d785c60 to your computer and use it in GitHub Desktop.
Fast naive automapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
namespace AutoMapperExpress | |
{ | |
public struct TypeMap : IEquatable<TypeMap> | |
{ | |
private readonly Type _sourceType; | |
private readonly Type _destinationType; | |
public Type Source { get { return _sourceType; } } | |
public Type Destination { get { return _destinationType; } } | |
public TypeMap(Type src, Type dst) | |
{ | |
_sourceType = src; | |
_destinationType = dst; | |
} | |
public override int GetHashCode() | |
{ | |
return _sourceType.GetHashCode() ^ _destinationType.GetHashCode(); | |
} | |
public bool Equals(TypeMap other) | |
{ | |
return _sourceType == other._sourceType && _destinationType == other._destinationType; | |
} | |
} | |
public interface IMapper | |
{ | |
TResult Map<TSource, TResult>(TSource source) | |
where TSource : class | |
where TResult : class, new(); | |
TResult Map<TResult>(object source) | |
where TResult : class, new(); | |
} | |
public static class ObjectExtensions | |
{ | |
public static TResult MapTo<TResult>(this object source) | |
where TResult : class, new() | |
{ | |
return ExpressMapper.Instance.Map<TResult>(source); | |
} | |
} | |
public sealed class ExpressMapper : IMapper | |
{ | |
private static readonly ExpressMapper StaticMapper = new ExpressMapper(); | |
public static IMapper Instance { get { return StaticMapper; } } | |
private readonly ConcurrentDictionary<TypeMap, Func<object, object>> _mappingFunctions; | |
public ExpressMapper() | |
{ | |
_mappingFunctions = new ConcurrentDictionary<TypeMap, Func<object, object>>(); | |
} | |
private Func<object, object> BuildMap(TypeMap typeMap) | |
{ | |
var sourceProperties = typeMap.Source.GetProperties(); | |
var destinationProperties = typeMap.Destination.GetProperties().ToDictionary(x => x.Name, x => x); | |
var assignments = new List<Expression>(); | |
var srcInput = Expression.Parameter(typeof(object), "srcArgument"); | |
var src = Expression.Variable(typeMap.Source, "src"); | |
var dst = Expression.Variable(typeMap.Destination, "dst"); | |
var mapMethod = GetType().GetMethods().First(x => x.Name == "Map" && x.IsGenericMethodDefinition && x.GetGenericArguments().Length == 2); | |
foreach (var srcProperty in sourceProperties) | |
{ | |
PropertyInfo dstProperty; | |
if (destinationProperties.TryGetValue(srcProperty.Name, out dstProperty) && dstProperty.CanWrite) | |
{ | |
Expression assignment; | |
if (dstProperty.PropertyType != srcProperty.PropertyType && | |
dstProperty.PropertyType.IsClass) | |
assignment = Expression.Assign(Expression.Property(dst, dstProperty), | |
Expression.Call(Expression.Constant(this), mapMethod.MakeGenericMethod(srcProperty.PropertyType, dstProperty.PropertyType), Expression.Property(src, srcProperty))); | |
else | |
assignment = Expression.Assign(Expression.Property(dst, dstProperty), Expression.Property(src, srcProperty)); | |
assignments.Add(assignment); | |
} | |
} | |
var result = Expression.Lambda<Func<object, object>>(Expression.Block(typeMap.Destination, new[] { src, dst }, | |
Expression.Assign(src, Expression.Convert(srcInput, src.Type)), | |
Expression.Assign(dst, Expression.New(dst.Type)), | |
Expression.Block(assignments), | |
dst), srcInput); | |
return result.Compile(); | |
} | |
public void Define<TSource, TResult>(Func<object, object> map) | |
{ | |
_mappingFunctions.AddOrUpdate(new TypeMap(typeof(TSource), typeof(TResult)), (TypeMap t) => map, (t, f) => map); | |
} | |
public TResult Map<TResult>(object source) | |
where TResult : class, new() | |
{ | |
if (source == null) | |
return null; | |
var typeMap = new TypeMap(source.GetType(), typeof(TResult)); | |
var map = _mappingFunctions.GetOrAdd(typeMap, BuildMap); | |
return (TResult)map(source); | |
} | |
public TResult Map<TSource, TResult>(TSource source) | |
where TSource : class | |
where TResult : class, new() | |
{ | |
if (source == null) | |
return null; | |
var typeMap = new TypeMap(typeof(TSource), typeof(TResult)); | |
var map = _mappingFunctions.GetOrAdd(typeMap, BuildMap); | |
return (TResult)map(source); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment