百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

分享教学项目:开源一个对象映射框架

bigegpt 2024-09-08 11:27 78 浏览

Maomi.Mapper

项目地址:https://github.com/whuanle/Maomi.Mapper
注:本项目用于教学目的,性能较差,请勿用于生产环境。

MaomiMapper 是一个使用表达式树构造生成对象成员映射的框架,即对象映射框架,用于配合笔者其它系列文章,用于教学目的。

笔者此系列教程还没有公开,是讲解如何编写各类框架的。

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

MamomiMapper 不是为了对标 AutoMapper,而是用于教学目的。

MaomiMapper 与 AutoMapper 对比:

MethodMeanErrorStdDevGen0Allocated
ASAutoMapper148.66 ns1.781 ns1.666 ns0.0362304 B
ASMaomiMapper6,562.87 ns14.360 ns13.433 ns0.26702265 B
_AutoMapper69.21 ns0.134 ns0.105 ns0.0191160 B
_MaomiMapper3,203.79 ns11.527 ns10.783 ns0.12211040 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

支持任何类型自动转换为 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"));
}


相关推荐

恢复软件6款汇总推荐,帮你减轻数据恢复压力!

在当今数字化生活中,数据丢失的风险如影随形。无论是误删文件、硬盘故障,还是遭遇病毒攻击,丢失的数据都可能给我们带来不小的麻烦。此时,一款优秀的数据恢复软件就成为了挽救数据的关键。今天,为大家汇总推荐...

中兴星星一号刷回官方原版recovery的教程

【搞科技教程】中兴星星一号的官方recovery也来说一下了,因为之前给大家分享过了第三方的recovery了,之前给大家分享的第三方recovery也是采用一键刷入的方式,如果细心的朋友会发现,之前...

新玩机工具箱,Uotan柚坛工具箱软件体验

以前的手机系统功能比较单调,各厂商的重视程度不一样,所以喜欢玩机的朋友会解锁手机系统的读写权限,来进行刷机或者ROOT之类的操作,让使用体验更好。随着现在的手机系统越来越保守,以及自身功能的增强,...

三星g906k刷recovery教程_三星g906k中文recovery下载

【搞科技教程】看到有一些机友在找三星g906k的第三方recovery,下面就来说一下详细的recovery的刷入方法了,因为手机只有有了第三方的recovery之后才可以刷第三方的root包和系统包...

中兴星星2号刷recovery教程_星星二号中文recovery下载

【搞科技教程】咱们的中兴星星2手机也就是中兴星星二号手机的第三方recovery已经出来了,并且是中文版的,有了这个recovery之后,咱们的手机就可以轻松的刷第三方的系统包了,如果没有第三方的re...

数据恢复软件有哪些值得推荐?这 6 款亲测好用的工具汇总请收好!

在数字生活中,数据丢失的阴霾常常突如其来。无论是误删工作文档、格式化重要磁盘,还是遭遇系统崩溃,都可能让我们陷入焦虑。关键时刻,一款得力的数据恢复软件便是那根“救命稻草”。今天,为大家精心汇总6...

中兴u956刷入recovery的教程(中兴e5900刷机)

【搞科技教程】这次主要来给大家说说中兴u956手机如何刷入第三方的recovery,因为第三方的recovery工具是咱们刷第三方rom包的基础,可是很我欠却不会刷,所以太这里来给大家整理了一下详细的...

联想A850+刷recovery教程 联想A850+第三方recovery下载

【搞科技教程】联想A850+的第三方recovery出来了,这个第三方的recovery是非常的重要的,比如咱们的手机要刷第三方的系统包的时候,都是需要用到这个第三方的recovery的,在网上也是有...

工具侠重大更新 智能机上刷机一条龙完成

工具侠是针对玩机的机油开发的一款工具,不管是发烧级别的粉丝,还是普通小白用户,都可以在工具侠上找到你喜欢的工具应用。这不,最新的工具侠2.0.16版本,更新了专门为小白准备的刷机助手工具,以及MTK超...

shift+delete删除的文件找回6种硬盘数据恢复工具

硬盘作为电脑的重要存储设备,如同一个巨大的数字仓库,承载着我们日常工作、学习和生活中的各种文件,从珍贵的照片、重要的工作文档到喜爱的视频、音乐等,都依赖硬盘来安全存放。但有时,我们可能会不小心用sh...

使用vscode+Deepseek 实现AI编程 基于Cline和continue

尊敬的诸位!我是一名专注于嵌入式开发的物联网工程师。关注我,持续分享最新物联网与AI资讯和开发实战。期望与您携手探寻物联网与AI的无尽可能。这两天deepseek3.0上线,据说编程能力比肩Cl...

详解如何使用VSCode搭建TypeScript环境(适合小白)

搭建Javascript环境因为TypeScript不能直接在浏览器上运行。它需要编译器来编译并生成JavaScript文件。所以需要首先安装好javascript环境,可以参考文章:https://...

使用VSCode来书写你的Jupyter Notebooks

现在你可以在VScode里面来书写你的notebook了,使用起来十分的方便。下面来给大家演示一下环境的搭建。首先需要安装一个jupyter的包,使用下面的命令安装:pip3install-ih...

使用VSCode模板提高Vue开发效率(vscode开发vue插件)

安装VSCode安装Vetur和VueHelper插件,安装完成后需要重启VScode。在扩展插件搜索框中找到如下Vetur和VueHelper两个插件,注意看图标。添加Vue模板打...

干货!VsCode接入DeepSeek实现AI编程的5种主流插件详解

AI大模型对编程的影响非常之大,可以说首当其冲,Cursor等对话式编程工具渐渐渗透到开发者的工作中,作为AI编程的明星产品,Cursor虽然好用,但是贵啊,所以咱们得找平替,最好免费那种。俗话说,不...