О чем речь?
Речь в этой заметке пойдет о библиотеках для автоматического копирования полей одного объекта в поля другого (мэппинг объектов). О том, для чего это надо можно почитать, например, тут.
Предлагаю рассмотреть следующие библиотеки, с помощью которых можно решить вышеописанную задачу:
1) AutoMapper
2) BLToolkit
3) EmitMapper
Все эти библиотеки отличает тот факт, что они используют Emit для генерации кода во время выполнения, и потенциально могут работать с эффективностью приближенной к ручному кодированию. Так ли это, предлагаю проверить.
AutoMapper
Сайт: automapper.codeplex.com
На данный момент это самая распространенная библиотека. Библиотека содержит приличный набор медодов для расширения и кастомизации мэппинга, а также поддерживает работу со сложными вложенными объектами. Хотя для работы с последними придется дополнительно сделать «приседания», предварительно зарегистрировав каждую пару сопоставляемых типов.
BLToolkit
Сайт: bltoolkit.net
Это не чистый Objects to Objects мэппер (скорее это легковесная ORM), хотя и поддерживает мэппинг одних объектов в другие. Но из-за того, что это не чистый Objects to Objects мэппер у него отсутствуют многе возможности присущие полноценным мэпперам таким как AutoMapper или EmitMapper. Библиотека не поддерживает сложные объекты, да и сам мэппинг не может быть каким-либо способом кастомизирован.
EmitMapper
Сайт: emitmapper.codeplex.com
Свежая разработка. Весьма гибкая библиотека, которая позволяет мэппить почти все что угодно куда угодно. Например, можно самому написать для этой библиотеки конфигурацию мэппера, которая считает данные из DbDataReader и запишет их в некоторый объект. На данный момент, это единственная библиотека из рассматриваемых, доступная под Silverlight (по крайней мере в официальных релизах).
Сравнение производительности.
Для сравнение производительности были написаны следующие тесты.
Простой тест.
Сопоставляются два простых класса, все поля которых имеют примитивные типы и без какой-либо дополнительной кастомизации. Это единственный тест, который может быть выполнен на BLToolkit. Код теста можно найти здесь.
Результаты (время работы в миллисекундах):
Auto Mapper | 36809 |
BLToolkit | 34533 |
Emit Mapper | 117 |
Handwritten Mapper | 37 |
По цифрам видно, что разница очень впечатляющая, но для лучшего представления масштаба предлагаю посмотреть на диаграмму:
Вложенные классы.
Сопоставляются два класса, имеющие сложную структуру. Код теста можно посмотреть здесь.
Результаты (время работы в миллисекундах):
Auto Mapper | 52238 |
Emit Mapper | 102 |
Handwritten Mapper | 97 |
BLToolkit | не поддерживается |
В данном тесте видно, что производительность Emit Mapper и ручного кода практически идентична.
Диаграмма:
Кастомизация.
В данном тесте проверяется скорость работы сложного кастомизированного сопоставления. В частности активно используются Custom constructors, Null substitution и Custom converters. Код теста доступен здесь
Результаты (время работы в миллисекундах):
Auto Mapper | 50142 |
Emit Mapper | 197 |
BLToolkit | не поддерживается |
Диграмма:
Выводы
Продемонстрированная разница в производительности является весьма существенной и меняет сам подход к использования автоматических объектых мэпперов. Если в случае с AutoMapper мы должны постоянно держать в уме требования к данному участку кода (как часто он используется), то в случае с EmitMapper ничего этого не требуется. Мы просто выполняем мэппинг там, где нам нужно и знаем, что с производительностью будет все нормально.
P.S.: В комментариях резонно спросили о причинах такой разницы в производительности. Чтобы избежать недоразумений, вот они:
1) В EmitMapper есть возможность заранее сгенерировать мэппер и где-то его сохранить (например, во время старта программы в статическом поле). В AutoMapper и BLToolkit мы вынуждены использовать одну единственную глобальную точку входа типа: «Map.ObjectToObject(foo)». А это затраты на синхронизацию, поиск по словарю и т.д.
2) AutoMapper был изначально спроектирован и написан для Reflection и лишь потом переведен на Emit. Из-за этого там осталось очень много ненужного оверхеда.
3) EmitMapper практически нигде не использует boxing/unboxing в отличии от его конкурентов. Да и вообще, код, который он генерирует весьма эффективен.