首页 > 其他分享 >分享教学项目:开源一个对象映射框架

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

时间:2023-11-22 10:31:41浏览次数:27  
标签:set option 映射 get Value class 开源 分享 public

Maomi.Mapper

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

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

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

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

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

MaomiMapper 与 AutoMapper 对比:

Method

Mean

Error

StdDev

Gen0

Allocated

ASAutoMapper

148.66 ns

1.781 ns

1.666 ns

0.0362

304 B

ASMaomiMapper

6,562.87 ns

14.360 ns

13.433 ns

0.2670

2265 B

_AutoMapper

69.21 ns

0.134 ns

0.105 ns

0.0191

160 B

_MaomiMapper

3,203.79 ns

11.527 ns

10.783 ns

0.1221

1040 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"));
	}

痴者工良(https://whuanle.cn)



标签:set,option,映射,get,Value,class,开源,分享,public
From: https://blog.51cto.com/u_10006690/8512715

相关文章

  • 个人征信报告修改软件工具,无密码编辑器,无痕制作pdf密码,代码分享仅供阅读学习
    正常情况我们导出的征信报告是代码密码的,或者是其他的一些PDF文件都是带密码,我们要编辑的话必须要输入密码才可以,不然只能仅仅阅读,右击编辑就会出现输入密码框,通过这个工具你可以把带有密码的PDF文档导入进来,它会自动输出到指定目录,然后就可以直接修改内容,不需要密码,注意:成品不提......
  • wxid批量转换微信号接口工具,自动转换二维码,开源API分享!
    这个是今天客户定制的,就是从微信群导出了很多WXID,然后实现通过WXID加好友,我就直接调用了微信的接口,说明一下这是微信公开的接口,不存在HOOK或者是逆向技术存在的,公开接口,任何人都可以调用,我就是把接口通过易语言实现了批量生成的功能效果。界面图:  WXID添加效果,不是微信号,是......
  • 手机APP开发的注意事项及基础代码分享
    随着智能手机的普及,手机APP的需求也日益增长,开发一款手机APP不仅需要创新的设计理念,还需要注意一些关键的开发事项,以确保APP的稳定性和可靠性,下面,我们将分享一些手机APP开发的注意事项及基础代码,帮助大家更好地进行APP开发。一、手机APP开发的注意事项1、确定目标用户群体:在开发APP......
  • 电子书阅读设备分享——meebook M6
    电子书阅读设备分享——meebookM6*作为爱看书的你,除了购买纸质书之外,平常看电子书的话喜欢用什么设备呢?手机、平板还是电纸书?*meebook这个品牌其实就是以前的博阅,甚至连说明书都还有博阅的痕迹~M6这台电纸书口碑非常不错,价格也就700来块,很亲民,可以说是6英......
  • qq附近人提取脚本插件,微信wxid附近人提取接口工具,易语言代码分享,POST方式学习教程
    其实打开Qq附近人后它会返回一个数据包我们只需要把这个数据包提取解析出来就可以提取对方的wxid或者是QQ号,通过这个WXID还能直接加好友,而且是免费的接口,我今天把基础源码和案例图发给大家,免费分享,没有HOOK也没有逆向,就抓包实现的,正常逻辑哈,并非是违规开发。框架图:  转换后......
  • 使用开源工具将windows家庭版切换到专业版
    说明工具名称开源地址:https://github.com/massgravel/Microsoft-Activation-Scripts官方简介:使用HWID/Ohook/KMS38/OnlineKMS激活方法的Windows和Office激活器,专注于开源代码和较少的防病毒检测。使用打开工具方法1-PowerShell(推荐)在Windows8.1/10/11......
  • centos7.9 部署FastDFS+Nginx本地搭建文件服务器 高性能的文件服务器集群 同时实现在
    前言FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线......
  • 29.8k star,推荐一个酷炫、强大的现代开源文件资源管理器
    先看张图,下载地址在文末:「Files」是一款为Windows设计的现代化文件管理器,它具有美观的界面、强大的功能和高效的操作。本文将介绍「Files」的基本信息、使用方法、特点和安装方式,并对其进行总结。「Files」简介「Files」是一个由社区驱动的开源项目,由数百名贡献者共同设计和......
  • Python深入分享之闭包
    闭包(closure)是函数式编程的重要的语法结构。函数式编程是一种编程范式(而面向过程编程和面向对象编程也都是编程范式)。在面向过程编程中,我们见到过函数(function);在面向对象编程中,我们见过对象(object)。函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性......
  • 激发创新,助力研究:CogVLM,强大且开源的视觉语言模型亮相
    激发创新,助力研究:CogVLM,强大且开源的视觉语言模型亮相CogVLM是一个强大的开源视觉语言模型(VLM)。CogVLM-17B拥有100亿视觉参数和70亿语言参数。CogVLM-17B在10个经典跨模态基准测试上取得了SOTA性能,包括NoCaps、Flicker30kcaptioning、RefCOCO、RefCOCO+、RefCO......