Реализация своего AutoMapper. C#

216
23 октября 2017, 23:01

Я использую в своем учебном проекте AutoMapper, но мне он кажется неудобным инструментом. Я сделал свою функцию для конвертации данных из ViewModel в Model и обратно. Переносятся только свойства.

public static class Mapper
{
    private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _propertiesDictionaries
        = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
    public static T Map<T>(object obj) where T : new()
    {
        var objProperties = GetProperties(obj.GetType());
        var resultPropertyes = GetProperties(typeof(T));
        var result = new T();
        foreach (var resultProperty in resultPropertyes)
        {
            if (objProperties.TryGetValue(resultProperty.Key, out var objProperty))
            {
                resultProperty.Value.SetValue(result, objProperty.GetValue(obj));
            }
        }
        return result;
    }
    private static Dictionary<string, PropertyInfo> GetProperties(Type objType)
    {
        if (!_propertiesDictionaries.TryGetValue(objType, out var propertiesInfoDictionary))
        {
            var infos = objType.GetProperties();
            propertiesInfoDictionary = new Dictionary<string, PropertyInfo>();
            foreach (var propertyInfo in infos)
            {
                propertiesInfoDictionary.Add(propertyInfo.Name, propertyInfo);
            }
            _propertiesDictionaries.Add(objType, propertiesInfoDictionary);
        }
        return propertiesInfoDictionary;
    }
}

Можно ли сделать мою функцию лучше/быстрее? Какие есть аналоги у AutoMapper?

Update 1 Версия с использованием Expression:

public static class MapperExpression
{
    private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _propertiesDictionaries
        = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
    private static readonly Dictionary<Type, Dictionary<Type, Func<object, object>>> _mappersDictionaries = new
        Dictionary<Type, Dictionary<Type, Func<object, object>>>();
    public static T Map<T>(object source) where T : new()
    {
        var targetType = typeof(T);
        var sourceType = source.GetType();
        if (_mappersDictionaries.TryGetValue(targetType, out var targetTypeMappers))
        {
            if (targetTypeMappers.TryGetValue(sourceType, out var mapper))
            {
                return (T)mapper.Invoke(source);
            }
            else
            {
                mapper = CreateMapper<T>(source.GetType());
                targetTypeMappers.Add(sourceType, mapper);
                return (T)mapper.Invoke(source);
            }
        }
        else
        {
            targetTypeMappers = new Dictionary<Type, Func<object, object>>();
            var mapper = CreateMapper<T>(source.GetType());
            targetTypeMappers.Add(source.GetType(), mapper);
            _mappersDictionaries.Add(targetType, targetTypeMappers);
            return (T)mapper.Invoke(source);
        }
    }
    private static Func<object, object> CreateMapper<T>(Type sourceType) where T : new()
    {
        var sourceProperties = GetProperties(sourceType);
        var targetPropertyes = GetProperties(typeof(T));
        var paramExpr = Expression.Parameter(typeof(object));
        var sourceExpr = Expression.Convert(paramExpr, sourceType);
        var bindings = new List<MemberBinding>();
        foreach (var targetProperty in targetPropertyes)
        {
            if (sourceProperties.TryGetValue(targetProperty.Key, out var sourceProperty))
            {
                bindings.Add(Expression.Bind(targetProperty.Value, Expression.Property(sourceExpr, sourceProperty)));
            }
        }
        var resultExpr = Expression.MemberInit(Expression.New(typeof(T)), bindings);
        var mapperExpr = Expression.Lambda<Func<object, object>>(resultExpr, paramExpr);
        return mapperExpr.Compile();
    }
    private static Dictionary<string, PropertyInfo> GetProperties(Type objType)
    {
        if (!_propertiesDictionaries.TryGetValue(objType, out var propertiesInfoDictionary))
        {
            var infos = objType.GetProperties();
            propertiesInfoDictionary = new Dictionary<string, PropertyInfo>();
            foreach (var propertyInfo in infos)
            {
                propertiesInfoDictionary.Add(propertyInfo.Name, propertyInfo);
            }
            _propertiesDictionaries.Add(objType, propertiesInfoDictionary);
        }
        return propertiesInfoDictionary;
    }
}

Сравнение первой версии со второй (.net core 2.0, 2000000 конвертаций):

Answer 1

Да, можно. Вместо прямой работы со свойствами составьте выражение:

 var paramExpr = Expression.Parameter(typeof(object));
 var sourceExpr = Expression.Convert(paramExpr, obj.GetType());
 var bindings = new List<MemberBinding>();
 foreach (var resultProperty in resultPropertyes) {
     if (ваша логика) {
         bindings.Add(Expression.Bind(resultProperty, источник));
     }
 }
 var resultExpr = Expression.MemberInit(Expression.New(typeof(T)), bindings);
 var mapperExpr = Expression.Lambda<Func<object,T>>(resultExpr, paramExpr);
 var mapper = mapperExpr.Compile();
 return mapper(obj);

В приведенном мной виду работать будет медленнее чем через рефлексию. Но если закешировать делегат mapper для пары ключей typeof(T), obj.GetType() наподобие того как вы сделали со списком свойств - выполняться он будет намного быстрее чем через рефлексию.

READ ALSO
Передача файла в контроллер

Передача файла в контроллер

Не приходят данные на контроллер

191
Не удалось найти тип или имя пространства имен Unit

Не удалось найти тип или имя пространства имен Unit

Помогите решить проблему, пожалуйстаВ unity работаю совсем не долго, как решать подобное?

270
lock объекта по Id

lock объекта по Id

Столкнулся со следующей проблемойИмеется некий метод,записывающий сущность в бд, примерно такой:

203
Как хранить заготовленный C# Dictionary?

Как хранить заготовленный C# Dictionary?

У меня есть около десяти тысяч строчек в таблице следующего формата:

238