Maomi.Mapper注本项目用于教学目的性能较差请勿用于生产环境。仓库地址https://github.com/whuanle/Maomi.MapperMaomiMapper 是一个使用表达式树构造生成对象成员映射的框架即对象映射框架。虽然 MaomiMapper 性能不啥样但是代码注释也写得很齐全适合读者研究反射、表达式树、类型转换等代码。MaomiMapper 与 AutoMapper 对比MethodMeanErrorStdDevGen0AllocatedASAutoMapper146.30 ns1.759 ns1.645 ns0.0362304 BASMaomiMapper817.46 ns6.467 ns6.049 ns0.0935784 BASDelegate668.56 ns5.050 ns4.724 ns0.0839704 B_AutoMapper67.56 ns0.438 ns0.410 ns0.0191160 B_MaomiMapper242.03 ns0.751 ns0.702 ns0.0315264 B_Delegate188.64 ns1.251 ns1.109 ns0.0267224 BAS 开头的方法表示有类型转换。测试使用的模型类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 TestBaseT { 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 : TestBaseint { } 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; }快速使用 MaomiMapperMaomiMapper 框架的使用比较简单示例如下var maomi new MaomiMapper(); maomi .BindTestValue, TestB() .BindTestValue, TestC() .BindTestValue, TestD(); maomi.MapTestValue, TestD(new TestValue());配置在映射对象时可以配置映射逻辑比如碰到成员是对象时是否开辟新对象是否映射私有成员等。使用方法如下var mapper new MaomiMapper(); mapper.BindTestA, 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 Funcobject, 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.BindTestA, TestC(option option.IsObjectReference false); mapper.BindTestA, TestD(option option.IsObjectReference false); } }此外可以继承实现 MapOptionAttribute 特性然后附加到类型中在扫描程序集映射时框架会自动配置。[AttributeUsage(AttributeTargets.Class, AllowMultiple false, Inherited false)] public class MyMapOptionAttribute : MapOptionAttribute { public override ActionMapOption MapOption _option; private ActionMapOption _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 .BindTestValue, 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.BindTestC, TestD( option { option.IncludePrivate true; }) .Map(a 111, b ValueC) .Build(); mapper.BindTestC, TestDD().Build();相当于b.ValueC 111在配置映射时可以调用Build()方法自动映射其它字段或属性。比如开发者只配置了.ValueA属性未配置ValueB、ValueC等则调用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.BindTestA, 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.BindTestA, TestB(option { option.IsObjectReference true; }).BuildAndReverse(option { option.IsObjectReference false; }); var a new TestA { Value new[] { 1, 2, 3 } }; var b mapper.MapTestA, TestB(a);MaomiMapper 可以处理大多数集合除了字典等类型。处理相同类型的集合public class TestC { public Listint Value { get; set; } } public class TestD { public Listint Value { get; set; } }var mapper new MaomiMapper(); mapper.BindTestC, TestD(option { option.IsObjectReference false; }).Build(); var a new TestA { Value new[] { 1, 2, 3 } }; var b mapper.MapTestA, TestB(a);相当于d.Value new Listint(); d.Value.AddRange(c.Value);也可以处理不同类型的集合public class TestE { public Listint Value { get; set; } } public class TestF { public IEnumerableint Value { get; set; } } public class TestG { public HashSetint Value { get; set; } }var mapper new MaomiMapper(); mapper.BindTestE, TestF(option { option.IsObjectReference false; }).Build(); var a new TestE { Value new Listint { 1, 2, 3 } }; var b mapper.MapTestE, TestF(a);以上 TestE、TestF、TestG 均可互转。值类型互转框架支持以下类型自动互转。Boolean SByte Byte Int16 UInt16 Int32 UInt32 Int64 UInt64 Single Double Decimal Charimage-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.BindTestA, 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.MapTestA, 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)); }