我之前文章提到过 MediatR
的作者 Jimmy Bogard
,他也是大名鼎鼎的对象映射框架 AutoMapper
的作者。AutoMapper 的功能强大,在 .NET 领域的开发者中有非常高的知名度和使用率。而今天老衣要提的是另外一款高性能对象映射框架:Mapster
。它轻巧便捷,功能也非常强大,关键是性能很高——有可能是 .NET 领域性能最好的。
我们先来看看性能
与 AutoMapper 相比,Mapster 在速度和内存占用方面表现更加优秀,下面是官方给出的稍早版本 6.0 的性能对比表:
Method | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|
'Mapster 6.0.0' | 108.59 ms | 1.198 ms | 1.811 ms | 31000.0000 | - | - | 124.36 MB |
'Mapster 6.0.0 (Roslyn)' | 38.45 ms | 0.494 ms | 0.830 ms | 31142.8571 | - | - | 124.36 MB |
'Mapster 6.0.0 (FEC)' | 37.03 ms | 0.281 ms | 0.472 ms | 29642.8571 | - | - | 118.26 MB |
'Mapster 6.0.0 (Codegen)' | 34.16 ms | 0.209 ms | 0.316 ms | 31133.3333 | - | - | 124.36 MB |
'ExpressMapper 1.9.1' | 205.78 ms | 5.357 ms | 8.098 ms | 59000.0000 | - | - | 236.51 MB |
'AutoMapper 10.0.0' | 420.97 ms | 23.266 ms | 35.174 ms | 87000.0000 | - | - | 350.95 MB |
从表中我们可以看出,即使在不使用高性能组件的情况下它的性能都可以获得 4 倍于 AutoMapper,却只需要 1/3 左右的内存占用,而在使用 Roslyn Compiler
、FEC (FastExpressionCompiler)
、Code generation
等组件后可以再进一步提升 2-3 倍的性能。 Code generation 方式几乎就是这个事儿极限了。你还有更快的手段吗?
在实际项目中的基本使用
首先从 Nuget 中引用最新版本的 Mapster 包:
dotnet add package Mapster
对象映射最多的场景就是两个实体定义的属性名是重叠对应的,那么此时的基本用法就非常简单:
var destObject = sourceObject.Adapt<Destination>();
注意我说的是实体定义,没有只限制类定义。Class、Record(有点小限制注意查阅官方文档)、Interface 等各种形式都可以哦,这是我非常喜欢的。当然了你的源是 IQueryable
的也可以!
不是类也不是接口,只是基本的简单类型是否可以呢?也可以!
var s = 123.Adapt<string>(); // 等同于: 123.ToString();
列表、数组、集合、包括各种接口的字典之间的映射,也可以: IList<T>
, ICollection<T>
, IEnumerable<T>
, ISet<T>
, IDictionary<TKey, TValue>
等等都可以!
只要C#支持类型转换的类型,那么在 Mapster 中也同样支持转换,而且像枚举与字符串之间的转换,.NET 自带的方式性能稍慢,Mapster 也针对性的做了优化,所以你实际生产中绝大部分就是类似上面这么一行代码就行了,够简单便捷吧 :D
在某些情况下,需要依赖注入,Mapster 提供了 IMapper
和 Mapper
来满足这个需求:
var result = mapper.Map<TDestination>(source);
映射配置
现实项目中难免会有一些自定义映射的需求,Mapster 提供了很强大的映射配置机制,可以通过映射配置解决你各种灵活需求。
我们可以使用 TypeAdapterConfig<TSource, TDestination>.NewConfig()
或 TypeAdapterConfig<TSource, TDestination>.ForType()
配置类型映射;
注意当调用 NewConfig 方法时,将会覆盖已存在的类型映射配置。
TypeAdapterConfig<TSource, TDestination>
.NewConfig()
.Ignore(dest => dest.Age)
.Map(dest => dest.FullName,
src => string.Format("{0} {1}", src.FirstName, src.LastName));
当然了你想让自己配置全局有效,可以通过对 TypeAdapterConfig.GlobalSettings
进行设置处理。
你有一些场景需要有条件规则?没问题,可以通过 When
方法来实现:
TypeAdapterConfig.GlobalSettings
.When((srcType, destType, mapType) => srcType == destType)
.Ignore("Id");
上面这个配置的意思是,应用全局范围当任何一个映射的源类型和目标类型相同时,不映射 Id 属性。
新版本中对接口只读属性映射的增强
最近刚刚发布对 Mapster 7.3.0 带来了一些新的增强:
- Switch expression by @SergerGood in #334
- Upgrade packages by @SergerGood in #333
- Include .NET 6.0 as Target Framework for Mapster.Tool by @kaizen365 in #390
- Updated Sample Code in Readme by @CoSJay in #379
- Simplify packaging and publishing NuGet packages, remove old framework monikers and upgrade to C# 10.0 by @andrerav in #405
- Add ability to compile all mappings and then throw AggregateException by @MisterOzzy in #363
- Init read-only properties when mapping with a non-readonly interface fixes #374 by @andrei-traktatovich in #375
其中最后一下对接口的只读属性映射增强,是我非常喜欢的,解决了在实际项目中的设计需求,省了不少事儿。
public interface ITarget
{
int GetOnlyProperty {get;}
int NormalProperty {get;set;}
}
public interface ITargetWithGetSetProperties
{
int GetOnlyProperty {get;}
int NormalProperty {get;set;}
}
上面这个代码中场景中,如果 ITarget 类型的对象的属性 GetOnlyProperty 带有一个非 0 值,并想 Map 为 ITargetWithGetSetProperties 类型的对象时,老版本会在映射后目标对象的 GetOnlyProperty 保留 int 类型的默认值 0,没做任何映射,新版本中解决了这个问题!
你可能会问“为什么会有这个需求?”,嗯,一个原因是因为接口可以多继承,而类只能单一继承,你品品,细品…… :D
其他
微信公众号文章不适合详细展开讨论和分享,本文主要是抛砖引玉。想详细了解这个框架的可以到官方代码库中去看一下 https://github.com/MapsterMapper/Mapster ,如果说英文阅读有点困难,可以到 https://github.com/rivenfx/Mapster-docs 看热心网友做到中文翻译版。