Skip to content

Instantly share code, notes, and snippets.

@GeirGrusom
Last active August 29, 2015 14:17
Show Gist options
  • Save GeirGrusom/bb91456e47a62d785c60 to your computer and use it in GitHub Desktop.
Save GeirGrusom/bb91456e47a62d785c60 to your computer and use it in GitHub Desktop.
Fast naive automapper
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