Reduced AutoMapper - Auto-Map 对象速度提高 180%






4.94/5 (22投票s)
一个全新的简化版 AutoMapper 库,可用于自动映射一个对象到另一个对象。与 AutoMapper 相比,ReducedAutoMapper 的速度快 180%。
引言
从 AutoMapper CodePlex 网页可以看出,AutoMapper
是一个对象到对象的映射器。对象到对象的映射通过将一种类型的输入对象转换为不同类型的输出对象来工作。它有大量的设置,有时很难配置。在我的项目中,我需要自动映射简单的对象,这些对象没有集合属性,只有一个由自定义属性类型组成的庞大树形结构——TestCase
对象,它有一个 TestStep
类型的属性等等。此外,在一些罕见的情况下,AutoMapper
无法正常工作。因此,我创建了 ReducedAutoMapper
,它只有 **150 行代码**,但运行速度比 AutoMapper
快 80%。
Reduced AutoMapper 详解
对象到对象映射器的主要目标是将对象 A 映射到对象 B。
原始对象类型 - **不可序列化**
public class FirstObject
{
public FirstObject()
{
}
public string FirstName { get; set; }
public string SecondName { get; set; }
public string PoNumber { get; set; }
public decimal Price { get; set; }
public DateTime SkipDateTime { get; set; }
public SecondObject SecondObjectEntity { get; set; }
public List<SecondObject> SecondObjects { get; set; }
public List<int> IntCollection { get; set; }
public int[] IntArr { get; set; }
public SecondObject[] SecondObjectArr { get; set; }
}
目标对象 - **可序列化**(属性相同,只添加了序列化属性)
[DataContract]
public class MapFirstObject
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string SecondName { get; set; }
[DataMember]
public string PoNumber { get; set; }
[DataMember]
public decimal Price { get; set; }
[DataMember]
public MapSecondObject SecondObjectEntity { get; set; }
public MapFirstObject()
{
}
}
对象到对象映射器的第一步是 **注册** 原始对象和目标对象之间的 **关系**。
private Dictionary<object, object> mappingTypes;
public Dictionary<object, object> MappingTypes
{
get
{
return this.mappingTypes;
}
set
{
this.mappingTypes = value;
}
}
public void CreateMap<TSource, TDestination>()
where TSource : new()
where TDestination : new()
{
if (!this.MappingTypes.ContainsKey(typeof(TSource)))
{
this.MappingTypes.Add(typeof(TSource), typeof(TDestination));
}
}
为了完成此任务,该类包含一个 mappingTypes Dictionary
,用于存储原始类型和目标类型之间的关系。通过泛型方法 CreateMap
,将类型添加到字典中。
注册示例
ReducedAutoMapper.Instance.CreateMap<FirstObject, MapFirstObject>();
主自动映射算法如何工作?
在其核心,ReducedAutoMapper
大量使用 反射 来获取与自动映射对象相关的信息。
public TDestination Map<TSource, TDestination>(
TSource realObject,
TDestination dtoObject = default (TDestination),
Dictionary<object, object> alreadyInitializedObjects = null,
bool shouldMapInnerEntities = true)
where TSource : class, new()
where TDestination : class, new()
{
if (realObject == null)
{
return null;
}
if (alreadyInitializedObjects == null)
{
alreadyInitializedObjects = new Dictionary<object, object>();
}
if (dtoObject == null)
{
dtoObject = new TDestination();
}
var realObjectType = realObject.GetType();
PropertyInfo[] properties = realObjectType.GetProperties();
foreach (PropertyInfo currentRealProperty in properties)
{
PropertyInfo currentDtoProperty = dtoObject.GetType().GetProperty(currentRealProperty.Name);
if (currentDtoProperty == null)
{
////Debug.WriteLine("The property {0} was not found
////in the DTO object in order to be mapped.
/// Because of that we skip to map it.", currentRealProperty.Name);
}
else
{
if (this.MappingTypes.ContainsKey
(currentRealProperty.PropertyType) && shouldMapInnerEntities)
{
object mapToObject = this.mappingTypes[currentRealProperty.PropertyType];
var types = new Type[] { currentRealProperty.PropertyType, (Type)mapToObject };
MethodInfo method = GetType().GetMethod("Map").MakeGenericMethod(types);
var realObjectPropertyValue = currentRealProperty.GetValue(realObject, null);
var objects = new object[]
{
realObjectPropertyValue,
null,
alreadyInitializedObjects,
shouldMapInnerEntities
};
if (objects != null && realObjectPropertyValue != null)
{
if (alreadyInitializedObjects.ContainsKey
(realObjectPropertyValue) && currentDtoProperty.CanWrite)
{
// Set the cached version of the same object (optimization)
currentDtoProperty.SetValue(dtoObject, alreadyInitializedObjects
[realObjectPropertyValue]);
}
else
{
// Add the object to cached objects collection.
alreadyInitializedObjects.Add(realObjectPropertyValue, null);
// Recursively call Map method again to get the new proxy object.
var newProxyProperty = method.Invoke(this, objects);
if (currentDtoProperty.CanWrite)
{
currentDtoProperty.SetValue(dtoObject, newProxyProperty);
}
if (alreadyInitializedObjects.ContainsKey(realObjectPropertyValue)
&& alreadyInitializedObjects[realObjectPropertyValue] == null)
{
alreadyInitializedObjects[realObjectPropertyValue] = newProxyProperty;
}
}
}
else if (realObjectPropertyValue == null && currentDtoProperty.CanWrite)
{
// If the original value of the object was null set null to the destination property.
currentDtoProperty.SetValue(dtoObject, null);
}
}
else if (!this.MappingTypes.ContainsKey(currentRealProperty.PropertyType))
{
// If the property is not custom type just set normally the value.
if (currentDtoProperty.CanWrite)
{
currentDtoProperty.SetValue
(dtoObject, currentRealProperty.GetValue(realObject, null));
}
}
}
}
return dtoObject;
}
首先,它获取源对象的属性。
var realObjectType = realObject.GetType();
PropertyInfo[] properties = realObjectType.GetProperties();
接下来,它会遍历这些属性。如果目标对象中不存在同名属性,则跳过。如果存在且不是我们的自定义类(而是 System
类,如 - string
、int
、DateTime
),则将其值设置为源属性的值。
else if (!this.MappingTypes.ContainsKey(currentRealProperty.PropertyType))
{
// If the property is not custom type just set normally the value.
if (currentDtoProperty.CanWrite)
{
currentDtoProperty.SetValue(dtoObject, currentRealProperty.GetValue(realObject, null));
}
}
如果属性的类型是 **自定义类型**,并且 **不存在** 于 **字典** 中,则 **不进行自动映射**。
否则,为了计算目标对象的新值,我们使用反射 **递归地** 调用泛型 **Map** 方法。
如果内部属性类型的值已经计算出来,则有一个优化。当一个已注册的目标类型被计算出来后,它的值会被放入 alreadyInitializedObjects
集合中,之后就不会再递归调用 Map
方法。
如果您需要自动映射对象集合,可以使用 ReducedAutoMapper
类的第三个方法 - MapList
。
public List<TDestination> MapList<TSource, TDestination>
(List<TSource> realObjects, Dictionary<object, object> alreadyInitializedObjects = null)
where TSource : class, new()
where TDestination : class, new()
{
List<TDestination> mappedEntities = new List<TDestination>();
foreach (var currentRealObject in realObjects)
{
TDestination currentMappedItem = this.Map<TSource,
TDestination>(currentRealObject, alreadyInitializedObjects: alreadyInitializedObjects);
mappedEntities.Add(currentMappedItem);
}
return mappedEntities;
}
比较 AutoMapper 和 ReducedAutoMapper
我创建了一个简单的控制台应用程序,其中初始化了具有超过 **1000** 个属性的巨大对象。创建的对象数量为 **100000**。
上面是第一个源类 - FirstObject
。下面是另外两个。
SecondObject
public class SecondObject
{
public SecondObject(string firstNameS, string secondNameS, string poNumberS, decimal priceS)
{
this.FirstNameS = firstNameS;
this.SecondNameS = secondNameS;
this.PoNumberS = poNumberS;
this.PriceS = priceS;
ThirdObject1 = new ThirdObject();
ThirdObject2 = new ThirdObject();
ThirdObject3 = new ThirdObject();
ThirdObject4 = new ThirdObject();
ThirdObject5 = new ThirdObject();
ThirdObject6 = new ThirdObject();
}
public SecondObject()
{
}
public string FirstNameS { get; set; }
public string SecondNameS { get; set; }
public string PoNumberS { get; set; }
public decimal PriceS { get; set; }
public ThirdObject ThirdObject1 { get; set; }
public ThirdObject ThirdObject2 { get; set; }
public ThirdObject ThirdObject3 { get; set; }
public ThirdObject ThirdObject4 { get; set; }
public ThirdObject ThirdObject5 { get; set; }
public ThirdObject ThirdObject6 { get; set; }
}
ThirdObject
{
public ThirdObject()
{
}
public DateTime DateTime1 { get; set; }
public DateTime DateTime2 { get; set; }
public DateTime DateTime3 { get; set; }
//.. it contains 996 properties more
public DateTime DateTime1000 { get; set; }
}
下面的代码使用 100000
个对象测试 ReducedAutoMapper
。
public class Program
{
static void Main(string[] args)
{
Profile("Test Reduced AutoMapper 10 Runs 10k Objects", 10, () => MapObjectsReduceAutoMapper());
System.Console.ReadLine();
}
static void Profile(string description, int iterations, Action actionToProfile)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++)
{
actionToProfile();
}
watch.Stop();
System.Console.WriteLine(description);
System.Console.WriteLine("Total: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
watch.ElapsedMilliseconds, watch.ElapsedTicks, iterations);
var avgElapsedMillisecondsPerRun = watch.ElapsedMilliseconds / iterations;
var avgElapsedTicksPerRun = watch.ElapsedMilliseconds / iterations;
System.Console.WriteLine("AVG: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
avgElapsedMillisecondsPerRun, avgElapsedTicksPerRun, iterations);
}
static void MapObjectsReduceAutoMapper()
{
List<FirstObject> firstObjects = new List<FirstObject>();
List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();
ReducedAutoMapper.Instance.CreateMap<FirstObject, MapFirstObject>();
ReducedAutoMapper.Instance.CreateMap<SecondObject, MapSecondObject>();
ReducedAutoMapper.Instance.CreateMap<ThirdObject, MapThirdObject>();
for (int i = 0; i < 10000; i++)
{
FirstObject firstObject =
new FirstObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)12.2, DateTime.Now,
new SecondObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)11.2));
firstObjects.Add(firstObject);
}
foreach (var currentObject in firstObjects)
{
MapFirstObject mapSecObj = ReducedAutoMapper.Instance.Map<FirstObject, MapFirstObject>(currentObject);
mapFirstObjects.Add(mapSecObj);
}
}
}
结果

下面的代码使用 100000
个对象测试 AutoMapper
。
public class Program
{
static void Main(string[] args)
{
Profile("Test Original AutoMapper 10 Runs 10k Objects", 10, () => MapObjectsAutoMapper());
System.Console.ReadLine();
}
static void Profile(string description, int iterations, Action actionToProfile)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++)
{
actionToProfile();
}
watch.Stop();
System.Console.WriteLine(description);
System.Console.WriteLine("Total: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
watch.ElapsedMilliseconds, watch.ElapsedTicks, iterations);
var avgElapsedMillisecondsPerRun = watch.ElapsedMilliseconds / iterations;
var avgElapsedTicksPerRun = watch.ElapsedMilliseconds / iterations;
System.Console.WriteLine("AVG: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
avgElapsedMillisecondsPerRun, avgElapsedTicksPerRun, iterations);
}
static void MapObjectsAutoMapper()
{
List<FirstObject> firstObjects = new List<FirstObject>();
List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();
AutoMapper.Mapper.CreateMap<FirstObject, MapFirstObject>();
AutoMapper.Mapper.CreateMap<SecondObject, MapSecondObject>();
AutoMapper.Mapper.CreateMap<ThirdObject, MapThirdObject>();
for (int i = 0; i < 10000; i++)
{
FirstObject firstObject =
new FirstObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)12.2, DateTime.Now,
new SecondObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)11.2));
firstObjects.Add(firstObject);
}
foreach (var currentObject in firstObjects)
{
MapFirstObject mapSecObj = AutoMapper.Mapper.Map<FirstObject, MapFirstObject>(currentObject);
mapFirstObjects.Add(mapSecObj);
}
}
}
结果

从上面的结果可以看出,ReducedAutoMapper 的性能比AutoMapper好 180%。
C# 系列到目前为止
1. 实现复制代码 C# 代码
2. MSBuild TCP IP 日志记录器 C# 代码
3. Windows 注册表读写 C# 代码
4. 运行时更改 .config 文件 C# 代码
5. 通用属性验证器 C# 代码
6. Reduced AutoMapper - Auto-Map Objects 加速 180%
7. C# 6.0 中 7 个很酷的新功能
8. 代码覆盖率类型 - C# 示例
9. MSTest 通过 MSTest.exe 包装器应用程序重新运行失败的测试
10. 在 Visual Studio 中高效安排 Usings 的提示
11. 19 个必须了解的 Visual Studio 键盘快捷键 – 第一部分
12. 19 个必须了解的 Visual Studio 键盘快捷键 – 第二部分
13. 在 Visual Studio 中根据生成配置指定程序集引用
14. .NET 的 15 个顶级未被充分利用的功能
15. .NET 的 15 个顶级未被充分利用的功能 第二部分
16. 在 C# 中轻松格式化货币的技巧
17. 正确断言 DateTime - MSTest NUnit C# 代码
18. 哪个更快 - 空合并运算符、GetValueOrDefault 还是条件运算符?
19. 基于规范的测试设计技术,用于增强单元测试
20. 使用 Lambda 表达式在 C# 中获取属性名
21. 使用 C# 的 9 个顶级 Windows 事件日志技巧
如果你喜欢我的文章,请随意订阅
另外,请点击这些分享按钮。谢谢!
源代码
这篇博文 - Reduced AutoMapper - Auto-Map Objects 加速 80% 最先出现在 Automate The Planet。
所有图片均来自DepositPhotos.com 购买,不能免费下载和使用。许可协议