本文的动机需求在于对任意一个类型MyClass,可以在不需要书写任何额外代码的同时自动进行深拷贝以及深比较,并且需要保证一定的性能。之前的方案是两年前使用Python正则自动生成代码,性能当然是最高的,但是使用起来比较麻烦:使用Python自动生成代码。最近学习了C#的IL以后,发现了一种不错的解决方案。
IL是C#编译后生成的一种中间代码,之后才会被JIT解释生成对应的机器码进行执行。同时,C#提供了手动书写IL,并转换为方法的API,具体使用方法不做过多赘述,可以参见之前的一篇文章,文中提供了利用IL Emit进行动态创建一个类型的方法:通过IL Emit来创建类型。
简而言之,利用IL可以书写任意类型的C#代码(因为C#本来就被编译生成IL),然后转化成运行时function,这样的性能基本相当于手写C#代码的速度。
需求是对一个类型生成对应的深拷贝代码,然后利用IL进行书写并生成,那么怎么递归获取内部所有成员变量呢,答案是利用反射。当然,反射显然比较慢,于是我们利用空间换时间的思路,将对应类型所生成的深拷贝方法保存在一张表里,下次直接使用即可。
于是整个流程如下:传入一个类型,检查表内是否已经存在对应深拷贝方法,如果有则直接返回;如果不存在,则利用反射递归查找所有的成员变量,之后利用IL生成相应的方法,存入表中并返回。(显然,第一次生成的时候会有一定开销)
思路其实比较简单,但是实际上细节和坑还是很多的:
1. 需要熟悉OpCodes码,这部分可以查阅资料进行了解。
2. 需要知道各种C#代码怎么转换成IL,这部分可以通过将ildasm将C#生成的dll转化成IL进行查看,可百度了解使用方式。
3. 需要验证生成的IL代码对应怎样的C#代码时,可以利用API将书写的IL代码生成dll,然后使用Reflector进行转化成C#代码,有时候不知道书写的IL代码为何不对时,这个方法非常好用。不过Reflector是收费的,据说ILSpy也可以使用,但是笔者没有用过,以后有机会试试。
4. 书写IL代码是一件困难的事,因为其语法类似于汇编,所以需要事先构建一个简单的框架来书写面向过程的代码,笔者提供的框架中,利用闭包简单的封装了方便使用的for和if功能,不然用汇编写这些确实有点繁琐。
5. Debug是一件困难的事。可以在每个OpCodes语句后面加Log,来观察生成的语句,并且书写同样的C#代码使用ildasm生成IL之后进行比对;或者使用第三点来比对C#代码。
笔者提供一个git作为参考,可直接使用。https://github.com/523810185/TypeCmpAndCloneGenerator