ITPub博客

首页 > IT职业 > IT职场 > .NET IL实现对象深拷贝

.NET IL实现对象深拷贝

IT职场 作者:zhjhome 时间:2021-09-09 09:07:01 0 删除 编辑

对于深拷贝,通常的方法是将对象进行序列化,然后再反序化成为另一个对象。例如在stackoverflow上有这样的解决办法:https://stackoverflow.com/questions/78536/deep-cloning-objects/78612#78612。 这种序列化的方式,对深拷贝来讲,无疑是一个性能杀手。

今天大家介绍一个深拷贝的框架 DeepCopy,github地址:https://github.com/ReubenBond/DeepCopy,它是从orleans框架改编过来的,实现逻辑非常简单。

框架的实现原理是通过IL代码生成字段拷贝的方法。IL的优点是可以绕过C#的语法规则,例如:访问私有对象以及给readonly字段赋值等。

在介绍框架前,先介绍一下IL相关的工具。

LINQPad中,运行并打开输出中的IL选项卡。

图片描述

使用像JetBrains的dotPeek这样的反编译/反汇编程序也是一个不错选择。您可以将编译的程序集在dotPeek中打开它来显示IL。

最后,ReSharper是不可或缺的工具。ReSharper带有一个方便的IL查看器。

图片描述

这些工具可以帮助您如何解决IL产生的问题,您也可以访问官方文档。

CopierGenerator.cs类的CreateCopier(Type type)方法中。让我们一步步揭秘:

首先创建一个DynamicMethod对象,它将保存创建的IL代码。在创建DynamicMethod对象时,必须告诉它签名是什么,在这里,它是一个通用的委托类型delegate T DeepCopyDelegate(T original, CopyContext context)。

var dynamicMethod = new DynamicMethod(
    type.Name + "DeepCopier",
    typeof(T), // 委托返回的类型
    new[] {typeof(T), typeof(CopyContext)}, // 委托的参数类型。
    typeof(CopierGenerator).Module,
    true);

var il = dynamicMethod.GetILGenerator(); 

IL将会变得相当复杂,因为它需要处理不可变的类型和值类型,接下来让我一点一点地说明。

// 定义一个变量来保存返回的结果。
il.DeclareLocal(type);

接下来,需要初始化传入类型的新实例到局部变量。有三种情况需要考虑,每种情况对应下面代码中的一个块:

  • 该类型是一个值类型(结构)。使用default(T)表达式来初始化它。
  • 该类型有一个无参数的构造函数。通过调用new T()初始化它。
  • 该类型没有无参数的构造函数。在这种情况下,我们借助 .Net 框架来解决,调用FormatterServices.GetUninitializedObject(type)。
   // 构造结果对象实例。
    var constructorInfo = type.GetConstructor(Type.EmptyTypes);
    if (type.IsValueType)
    {
        // 值类型可以直接初始化。
        // C#: result = default(T);
        il.Emit(OpCodes.Ldloca_S, (byte)0);
        il.Emit(OpCodes.Initobj, type);
    }
    else if (constructorInfo != null)
    {
        // 如果存在默认构造函数,则直接使用默认的参数。
        // C#: result = new T();
        il.Emit(OpCodes.Newobj, constructorInfo);
        il.Emit(OpCodes.Stloc_0);
    }
    else
    {
        // 如果没有默认构造函数的存在,使用GetUninitializedObject创建实例。
        // C#: result = (T)FormatterServices.GetUninitializedObject(type);
        il.Emit(OpCodes.Ldtoken, type);
        il.Emit(OpCodes.Call, DeepCopier.MethodInfos.GetTypeFromHandle);
        il.Emit(OpCodes.Call, this.methodInfos.GetUninitializedObject);
        il.Emit(OpCodes.Castclass, type);
        il.Emit(OpCodes.Stloc_0);
    }

在本地创建一个用于保存结果的变量,它是传入类型的新实例。在我们做任何事情之前,我们必须记录新创建对象的引用。将每个参数按顺序推入堆栈,并使用OpCodes.Call来调用context.RecordObject(original, result)。使用OpCodes.Call来调用CopyContext.RecordObject方法,因为CopyContext是一个sealed类,否则会使用OpCodes.Callvirt。

// 值类型的实例不会存在多次引用的问题,
// 所以只在上下文中记录引用类型。
if (!type.IsValueType)
{
    // 记录对象引用。
    // C#: context.RecordObject(original, result);
    il.Emit(OpCodes.Ldarg_1); // 参数:context
    il.Emit(OpCodes.Ldarg_0); // 参数数:original
    il.Emit(OpCodes.Ldloc_0); // 本地用来保存结果的变量
    il.Emit(OpCodes.Call, this.methodInfos.RecordObject);
}

枚举对象上的每一个字段并生成代码,将字段的值复制到结果变量中。过程如下:

// 复制每一个字段的值。
foreach (var field in this.copyPolicy.GetCopyableFields(type))
{
    // 加载结果对象的引用。
    if (type.IsValueType)
    {
        // 值类型需要通过地址来加载,而不是复制到堆栈上。
        il.Emit(OpCodes.Ldloca_S, (byte)0);
    }
    else
    {
        il.Emit(OpCodes.Ldloc_0);
    }

    // 加载原始对象字段的值。
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldfld, field);

    // 如果是不可变类型则直接赋值,否则需要深拷贝字段。
    if (!this.copyPolicy.IsShallowCopyable(field.FieldType))
    {
        // 复制字段使用泛型方法 DeepCopy.Copy(T original, CopyContext context)
        // C#: Copy(field)
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Call, this.methodInfos.CopyInner.MakeGenericMethod(field.FieldType));
    }

    // 将复制的值赋给结果对象的字段。
    il.Emit(OpCodes.Stfld, field);
}

返回结果并通过CreateDelegate构建委托,下一步可以直接使用。

// C#: return result;
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret);

return dynamicMethod.CreateDelegate(typeof(DeepCopyDelegate)) as DeepCopyDelegate;
DeepCopier.cs;

当然还有很多需要优化的细节,大家可以在github上提出您的宝贵意见。

参考内容:

https://reubenbond.github.io/posts/codegen-2-il-boogaloo

作者:Sweet Tang
本文地址:http://www.cnblogs.com/tdfblog/p/DeepCopy-By-IL.html

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/2530/viewspace-2799633/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-08-16

  • 博文量
    102
  • 访问量
    952