news 2026/4/19 6:20:06

.NET 高级开发 | 手写一个对象映射框架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.NET 高级开发 | 手写一个对象映射框架

Maomi.Mapper

注:本项目用于教学目的,性能较差,请勿用于生产环境。

仓库地址:https://github.com/whuanle/Maomi.Mapper

MaomiMapper 是一个使用表达式树构造生成对象成员映射的框架,即对象映射框架。

虽然 MaomiMapper 性能不啥样,但是代码注释也写得很齐全,适合读者研究反射、表达式树、类型转换等代码。

MaomiMapper 与 AutoMapper 对比:

Method

Mean

Error

StdDev

Gen0

Allocated

ASAutoMapper

146.30 ns

1.759 ns

1.645 ns

0.0362

304 B

ASMaomiMapper

817.46 ns

6.467 ns

6.049 ns

0.0935

784 B

ASDelegate

668.56 ns

5.050 ns

4.724 ns

0.0839

704 B

_AutoMapper

67.56 ns

0.438 ns

0.410 ns

0.0191

160 B

_MaomiMapper

242.03 ns

0.751 ns

0.702 ns

0.0315

264 B

_Delegate

188.64 ns

1.251 ns

1.109 ns

0.0267

224 B

AS 开头的方法表示有类型转换。

测试使用的模型类:

public class TestValue { public bool ValueA { get; set; } = true; public sbyte ValueB { get; set; } = 1; public byte ValueC { get; set; } = 2; public short ValueD { get; set; } = 3; public ushort ValueE { get; set; } = 4; public int ValueF { get; set; } = 5; public uint ValueG { get; set; } = 6; public long ValueH { get; set; } = 7; public ulong ValueI { get; set; } = 8; public float ValueJ { get; set; } = 9; public double ValueK { get; set; } = 10; public decimal ValueL { get; set; } = 11; public char ValueM { get; set; } = (Char)12; } public class TestB { public bool ValueA { get; set; } = true; public sbyte ValueB { get; set; } = 1; public byte ValueC { get; set; } = 2; public short ValueD { get; set; } = 3; public ushort ValueE { get; set; } = 4; public int ValueF { get; set; } = 5; public uint ValueG { get; set; } = 6; public long ValueH { get; set; } = 7; public ulong ValueI { get; set; } = 8; public float ValueJ { get; set; } = 9; public double ValueK { get; set; } = 10; public decimal ValueL { get; set; } = 11; public char ValueM { get; set; } = (Char)12; } public class TestBase<T> { public T ValueA { get; set; } public T ValueB { get; set; } public T ValueC { get; set; } public T ValueD { get; set; } public T ValueE { get; set; } public T ValueF { get; set; } public T ValueG { get; set; } public T ValueH { get; set; } public T ValueI { get; set; } public T ValueJ { get; set; } public T ValueK { get; set; } public T ValueL { get; set; } } public class TestC : TestBase<int> { } public class TestD { public bool ValueA { get; set; } = true; public sbyte ValueB { get; set; } = 1; public byte ValueC { get; set; } = 2; public short ValueD { get; set; } = 3; public ushort ValueE { get; set; } = 4; public int ValueF { get; set; } = 5; public uint ValueG { get; set; } = 6; public long ValueH { get; set; } = 7; public ulong ValueI { get; set; } = 8; public float ValueJ { get; set; } = 9; public double ValueK { get; set; } = 10; public decimal ValueL { get; set; } = 11; public char ValueM { get; set; } = (Char)12; }

快速使用 MaomiMapper

MaomiMapper 框架的使用比较简单,示例如下:

var maomi = new MaomiMapper(); maomi .Bind<TestValue, TestB>() .Bind<TestValue, TestC>() .Bind<TestValue, TestD>(); maomi.Map<TestValue, TestD>(new TestValue());

配置

在映射对象时,可以配置映射逻辑,比如碰到成员是对象时,是否开辟新对象,是否映射私有成员等。

使用方法如下:

var mapper = new MaomiMapper(); mapper.Bind<TestA, TestB>(option => { option.IsObjectReference = false; }).Build();

每个类型映射都可以单独配置一个 MapOption。

MapOption 类型:

/// <summary> /// 映射配置 /// </summary> public class MapOption { /// <summary> /// 包括私有字段 /// </summary> public bool IncludePrivate { get; set; } = false; /// <summary> /// 自动映射,如果有字段/属性没有配置映射规则,则自动映射 /// </summary> public bool AutoMap { get; set; } = true; /// <summary> /// 如果属性字段是对象且为相同类型,则保持引用。 <br /> /// 如果设置为 false,则会创建新的对象,再对字段逐个处理。 /// </summary> public bool IsObjectReference { get; set; } = true; /// <summary> /// 配置时间转换器。<br /> /// 如果 b.Value 是 DateTime,而 a.Value 不是 DateTime,则需要配置转换器,否则会报错。 /// </summary> /// <value></value> public Func<object, DateTime>? ConvertDateTime { get; set; } }

自动扫描

MaomiMapper 支持扫描程序集中的对象映射,有两种方法可以配置。

第一种方法是使用特性类,标识该类型可以转换为何种类型。

如下代码所示,TestValueB 标识了其可以映射为 TestValueA 类型。

public class TestValueA { public string ValueA { get; set; } = "A"; public string ValueB { get; set; } = "B"; public string ValueC { get; set; } = "C"; } [Map(typeof(TestValueA), IsReverse = true)] public class TestValueB { public string ValueA { get; set; } public string ValueB { get; set; } public string ValueC { get; set; } }

第二种方法是实现 IMapper,在文件中配置映射规则。

public class MyMapper : IMapper { public override void Bind(MaomiMapper mapper) { mapper.Bind<TestA, TestC>(option => option.IsObjectReference = false); mapper.Bind<TestA, TestD>(option => option.IsObjectReference = false); } }

此外,可以继承实现 MapOptionAttribute 特性,然后附加到类型中,在扫描程序集映射时,框架会自动配置。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class MyMapOptionAttribute : MapOptionAttribute { public override Action<MapOption> MapOption => _option; private Action<MapOption> _option; public MyMapOptionAttribute() { _option = option => { option.IsObjectReference = false; }; } } [MyMapOption] [Map(typeof(TestB), IsReverse = true)] public class TestA { public string ValueA { get; set; } = "A"; public string ValueB { get; set; } = "B"; public string ValueC { get; set; } = "C"; public TestValueA Value { get; set; } }

配置字段映射

可以使用.Map配置一个字段的映射规则。

maomi .Bind<TestValue, TestB>() .Map(a => a.ValueC + 1, b => b.ValueC).Build()

相当于:

b.ValueC = a.ValueC + 1

如果有私有字段需要映射,可以使用名称字段。

public class TestD { public string ValueA { get; set; } public string ValueB; private string ValueC { get; set; } private string ValueD; } public class TestDD { public string ValueA { get; set; } public string ValueB; public string ValueC { get; set; } public string ValueD; }
var mapper = new MaomiMapper(); var build = mapper.Bind<TestC, TestD>( option => { option.IncludePrivate = true; }) .Map(a => "111", b => "ValueC") .Build(); mapper.Bind<TestC, TestDD>().Build();

相当于:

b.ValueC = "111"

在配置映射时,可以调用Build()方法,自动映射其它字段或属性。比如开发者只配置了.ValueA属性,未配置ValueBValueC等,则调用Build()时,框架会补全其它属性对应的映射。如果未配置,框架则在第一次使用对象映射时自动调用。

如果需要反向映射,可以使用BuildAndReverse()

.BuildAndReverse(option => { option.IsObjectReference = false; });

可以忽略字段映射。

// b.V = a.V + "a" .Map(a => a.V + "a", b => b.V) // 忽略 V1 .Ignore(x => x.V1) // b.V2 = a.V .Map(a => a.V, b => "V2") // b.V3 = "666"; .Map(a => "666", b => "V3") .Build();

对象映射

有以下模型类:

public class TestValue { public string ValueA { get; set; } = "A"; public string ValueB { get; set; } = "B"; public string ValueC { get; set; } = "C"; } public class TestA { public TestValue Value { get; set; } } public class TestB { public TestValue Value { get; set; } }

TestA 和 TestB 类型中,均有 TestValue 类型的属性,框架默认使用引用赋值,示例:

testB.Value = testA.Value

两个对象的 Value 属性引用了同一个对象。

如果需要开辟新的实例,可以使用:

var mapper = new MaomiMapper(); mapper.Bind<TestA, TestB>(option => { // 开辟新的实例 option.IsObjectReference = false; }).Build();

如果两者的 Value 属性是不同类型对象,则框架也会自动映射。如:

public class TestA { public TestValueA Value { get; set; } } public class TestB { public TestValueB Value { get; set; } }

TestValueA、TestValueB 均为对象类型时,框架会自动映射下一层。

数组和集合映射

MaomiMapper 只能处理相同类型的数组,并且使用直接赋值的方法。

public class TestA { public int[] Value { get; set; } } public class TestB { public int[] Value { get; set; } }
var mapper = new MaomiMapper(); mapper.Bind<TestA, TestB>(option => { option.IsObjectReference = true; }).BuildAndReverse(option => { option.IsObjectReference = false; }); var a = new TestA { Value = new[] { 1, 2, 3 } }; var b = mapper.Map<TestA, TestB>(a);

MaomiMapper 可以处理大多数集合,除了字典等类型。

处理相同类型的集合:

public class TestC { public List<int> Value { get; set; } } public class TestD { public List<int> Value { get; set; } }
var mapper = new MaomiMapper(); mapper.Bind<TestC, TestD>(option => { option.IsObjectReference = false; }).Build(); var a = new TestA { Value = new[] { 1, 2, 3 } }; var b = mapper.Map<TestA, TestB>(a);

相当于:

d.Value = new List<int>(); d.Value.AddRange(c.Value);

也可以处理不同类型的集合:

public class TestE { public List<int> Value { get; set; } } public class TestF { public IEnumerable<int> Value { get; set; } } public class TestG { public HashSet<int> Value { get; set; } }
var mapper = new MaomiMapper(); mapper.Bind<TestE, TestF>(option => { option.IsObjectReference = false; }).Build(); var a = new TestE { Value = new List<int> { 1, 2, 3 } }; var b = mapper.Map<TestE, TestF>(a);

以上 TestE、TestF、TestG 均可互转。

值类型互转

框架支持以下类型自动互转。

Boolean SByte Byte Int16 UInt16 Int32 UInt32 Int64 UInt64 Single Double Decimal Char
image-20231015164629846

支持任何类型自动转换为 string,但是不支持 string 转换为其它类型。

对于时间类型的处理,可以手动配置转换函数:

public class TestA { public string Value { get; set; } } public class TestB { public DateTime Value { get; set; } } [Fact] public void AS_Datetime() { var mapper = new MaomiMapper(); mapper.Bind<TestA, TestB>(option => { // 配置转换函数 option.ConvertDateTime = value => { if (value is string str) return DateTime.Parse(str); throw new Exception("未能转换为时间"); }; }).Build(); var date = DateTime.Now; var a = mapper.Map<TestA, TestB>(new TestA() { Value = date.ToString() }); Assert.Equal(date.ToString("yyyy/MM/dd HH:mm:ss"), a.Value.ToString("yyyy/MM/dd HH:mm:ss")); }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/19 6:19:41

StructBERT零样本分类-中文-base详细步骤:Web界面调用+置信度解读指南

StructBERT零样本分类-中文-base详细步骤&#xff1a;Web界面调用置信度解读指南 1. 模型介绍&#xff1a;什么是StructBERT零样本分类 StructBERT零样本分类是阿里达摩院专门为中文文本处理开发的一款智能分类工具。这个模型最大的特点是"零样本"——也就是说&…

作者头像 李华
网站建设 2026/4/19 6:19:16

机器人操作系统ROS的架构分析与应用开发

机器人操作系统ROS的架构分析与应用开发 随着人工智能和机器人技术的快速发展&#xff0c;机器人操作系统&#xff08;Robot Operating System, ROS&#xff09;已成为机器人开发领域的核心工具之一。ROS以其模块化、分布式和开源的特点&#xff0c;为机器人应用开发提供了强大…

作者头像 李华
网站建设 2026/4/19 6:04:22

春节必备神器:春联生成模型-中文-base 一键生成专属春联

春节必备神器&#xff1a;春联生成模型-中文-base 一键生成专属春联 1. 传统与现代的完美结合 春节贴春联是中华民族延续千年的传统习俗&#xff0c;但现代人常常面临两个难题&#xff1a;要么手写春联缺乏专业水准&#xff0c;要么购买的印刷品千篇一律缺乏个性。现在&#…

作者头像 李华
网站建设 2026/4/19 6:03:24

千问3.5-2B软件测试用例智能生成与缺陷报告分析

千问3.5-2B软件测试用例智能生成与缺陷报告分析 1. 引言&#xff1a;测试工程师的日常痛点 每个测试工程师都经历过这样的场景&#xff1a;面对几十页的需求文档&#xff0c;需要手工编写数百个测试用例&#xff1b;或是翻看堆积如山的缺陷报告&#xff0c;却难以总结出系统性…

作者头像 李华