Pull to refresh

Comments 19

У данного маппера есть один недостаток (весьма широко распространенный), которого очень хотелось бы избежать — требование наличия конструктора по умолчанию и свойств, доступных для записи.
Для DTO это нормально, а для бизнес-объекта — хуже маппинга вручную.
Вне всякого сомнения, Кирилл. Это исключительно POC для сравнения базовой функциональности. Если говорить об инициализации экземпляров, у которого отсутствует конструктор по умолчанию, то тогда это или дополнительные конфигурации или Resolve.

Можно обойти это ограничение с помощью protected-конструктора и protected setter'ов. Не совсем элегантно, но mapper сможет дотянуться. Поправьте, если я ошибаюсь и через expression такой делегат не скомпилируешь.

Я бы сказал что это ненормально даже для DTO, я например использую записи в F# для определения DTO, кратко и без геммора с поддержкой Equals и HashCode. В решениях с max immutability такие подходы c mapper увы, не годятся.

Для быстродействия следующий код должен выполняться один раз, а не в цикле


    var key = new TypeTuple(typeof(TSource), typeof(TDest));
    var activator = GetMap(key);

т.е. код вызывающий преобразование должен быть расширен для получения оптимального метода преобразования


    mapper.Register<CustomerDto, Customer>();
    var map = mapper.GetMap<CustomerDto, Customer>();

    var customer = map(dto);
А хранить `map` вы где будете и с каким ключём? :)

Это подходит для того случая, когда перед стоит задача сделать маппинг коллекции здесь и сейчас, однако, ровно такой же код может быть реализован в методе маппинга коллекций самого маппера:
mapper.Map<List<CustomerDTO>, List<Customer>>()

В поле класса который этот маппер использует.

В таком случае «маппер» для каждой пары типов у вас будет свой, а не хватать им будет ровно одной единственной функциональности-
ObjectBuilder.GetActivator<TSource, TDest>()
Ничего не имею против такого подхода, но в таком случае вам в качестве зависимостей нужен не столько маппер, сколько библиотека по компиляции плана создания экземпляра требуемого типа.

Еще пара замечаний по дизайну.


  1. Сейчас маппер имеет изменяемое состояние.
    Логично разделить его на билдер (где можно делать регистрации) и маппер (потокобезопасный объект без состояния, можно только использовать ранее зарегистрированное).
  2. Сейчас маппер имеет неопределенный интерфейс (эквивалент локатора сервисов) — результат маппинга определяется не контрактом, а ранее сделанными регистрациями.
    Логично сделать не единый маппер с множеством регистраций и обобщенными методами, а обобщенный маппер (каждый на одну пару типов). Такой дизайн позволит полностью избавиться от изменяемого состояния (все что сейчас делается при регистрации можно будет реализовать прямо в конструкторе) и сделать зависмосоти от маппинга прозрачными (сразу видны требуемые типы).
1. Другими словами вынести состояние в отдельный тип, например, конфигурации?

2. Не совсем понял. Можно пояснения в виде кода?
Не совсем понял. Можно пояснения в виде кода?

public interface IMapper<TSource, TDest>
{
    TDest Map(TSource source); 
}

public interface IMapperBuilder
{
    IMapper<TSource, TDest> Build<TSource, TDest>();
}

Я правильно понимаю, что Ваш маппер НЕ поддерживает:


  • маппинг вложенных свойств
  • маппинг коллекций
  • маппинг на существующий объект (обновление)
  • генерацию проекций для IQueryable<T>

И при этом (судя по Вашим бенчмаркам) всего на 10% бытрее того же Mapster, который все это умеет?


Еще было бы неплохо сравнить с EmitMapper.


А для ускорения маппинга вместо Expression.Compile можно использовать FastExpressionCompiler.


Суть проекта в компиляции expression tree напрямую, без создания анонимной assembly для запуска в песочнице (как это делает Expression.Compile).

Совершенно верно. Вероятно я ввёл читателей в заблуждение, однако, цели написать полноценный маппер не стояло (впрочем, даже если и так, то был выбран итеративный подход).

В первую очередь было интересно реализовать совершенно базовый функционал наиболее оптимальным способом.

Добавлю EmitMapper в сравнение и попробую FastExpressionCompiler. Спасибо.
В первую очередь было интересно реализовать совершенно базовый функционал наиболее оптимальным способом.


Написание готового к продакшину маппера задача крайне нетривиальная и требует очень хорошо знания дотнета и всех его gotchas, либо накопления достаточного опыта в процессе постепенной разработки и фидбека пользователей. Это проект скорее POC как быстрее всего вызвать конструктор, чем готовый к использованию где либо набор базового функционала для маппера. Задача маппера удобный и понятный апи, универсальность(множество сфер применения), кастомизируемость, позволяющая избегать дубликатов кода, удобная расширяемость — известные на рынке вендоры годами накапливают опыт работы с ошибками, переиспользования конфигураций, добавления точек расширяемость, обработки частных случаев и в конце концов ускорения работы.

Использование Expression для движка и кеширования для ускорения это пожалуй одни из самых банальных и тривиальных вещей, с которыми стакливаються нынче разработчики мапперов.

Единожды была необходимость для одного из крупных корп заказчиков написать и развивать подобный проект на протяжении нескольких лет для множества проектов(причина созданий — вопрос бюрократии), который заменял automapper/dapper — сделать его соизмеримо быстрым по функционалу в соотвествующих задачах было куда проще — чем думать прорабатывать апи и решать описанные выше проблемы — это то, что требует длительного накопления опыта ииспользования в продакшине.

Сейчас на рынке достаточно удобных мапперов я стараюсь всегда использовать их для своих задач — ко всему остальному отношусь скептически, идея написания маппере для многих люди одна из самых простых где очень многие пытаются просто сделать свой велосипед аппелируя к скорости.

FastExpressionCompiler по крайней мере выполняет компиляцию конструкторов точно также как DynamicMethodILGenerator у автора.
К сожалению заиспользовать EmitMapper для .NET Core 2.0 не удалось по следующей причине:

System.MissingMethodException: Method not found: 'System.Reflection.Emit.AssemblyBuilder System.AppDomain.DefineDynamicAssembly(System.Reflection.AssemblyName, System.Reflection.Emit.AssemblyBuilderAccess)'
А при чём тут .NET Standard? Вы же только одну его реализацию, .NET Core используете.
Ну и необходимость лезть в исходники, чтобы понять, что скрывается за именами в бенчмарках — за гранью бобра и козла.

А почему бы не написать генератор мапинговых методов на Roslyn (для нас ведь главное уйти от написания рутинного кода)? Это сэкономит время старта, добавит гибкость (расширять мапинг можно будет обычным C# кодом) и можно даже сделать проверку вызова незарегистрированных мапингов при компиляции.

Интересная мысль, похоже это заявка на статью от вас :)
Only those users with full accounts are able to leave comments. Log in, please.