C# 与手动设置属性相比,DeepClone的性能(使用二进制序列化)

C# 与手动设置属性相比,DeepClone的性能(使用二进制序列化),c#,performance,clone,C#,Performance,Clone,我有一个对象集合,我试图克隆这个集合,并试图理解不同方法的性能含义 集合中的对象大约有20个属性—所有字符串、int、float(该对象内部没有任何嵌套对象)。这两种方法是: 创建DeepClone()方法: } 我得到了非常不一致的结果,所以我想得到人们对以下方面的反馈: 一个应该比另一个快得多吗?我本以为选择二,但我的测试似乎不支持这一点,所以我试图找出我是否做错了什么 有什么方法可以更快地做到这一点吗 首先,BinaryFormatter路由肯定要慢一些,因为它使用反射来获取/设置属性。最

我有一个对象集合,我试图克隆这个集合,并试图理解不同方法的性能含义

集合中的对象大约有20个属性—所有字符串、int、float(该对象内部没有任何嵌套对象)。这两种方法是:

  • 创建DeepClone()方法:

    }

  • 我得到了非常不一致的结果,所以我想得到人们对以下方面的反馈:

  • 一个应该比另一个快得多吗?我本以为选择二,但我的测试似乎不支持这一点,所以我试图找出我是否做错了什么

  • 有什么方法可以更快地做到这一点吗


  • 首先,BinaryFormatter路由肯定要慢一些,因为它使用反射来获取/设置属性。最常用的方法是将IClonable接口与复制构造函数结合使用

    class A : ICloneable
    {
        private readonly int _member;
    
        public A(int member)
        {
            _member = member;
        }
    
        public A(A a)
        {
            _member = a._member;
        }
    
        public object Clone()
        {
            return new A(this);
        }
    }
    
    当然,严格来说,您只需要复制构造函数,这应该是最快的方法。如果对象很简单,则应尝试使用内置函数

    class A : ICloneable
    {
        private readonly int _member;
    
        public A(int member)
        {
            _member = member;
        }
    
        public object Clone()
        {
            return MemberwiseClone();
        }
    }
    

    同时,我写了一篇文章,看看MemberwiseClone()是否比使用复制构造函数快或慢。你可以找到它。我发现MemberwiseClone实际上比CopyConstructor慢得多,至少在小型类上是这样。请注意,使用BinaryFormatter非常慢。

    为此使用序列化是一个非常聪明的想法。但是,一个接一个地设置属性也应该同样快,而且大多数情况下(对象非常小的情况除外)都必须更快—您所做的工作更少,并且不使用反射


    您能否重现一种情况,即使用序列化比“手动”复制属性快?

    序列化肯定比仅使用属性分配慢

    二进制序列化更可取,因为代码容易维护


    相反,属性分配更好,因为可能是在克隆过程中,您不希望克隆对象的所有属性,而是希望能够对它们进行序列化。简言之,您对流程有更多的控制权

    在我之前的角色中,我们研究了这个问题,因为我们正在缓存对象,希望在从缓存中分发对象之前克隆它们

    我们做了一些详细的基准测试,发现属性设置总是比
    BinaryFormatter
    方法快至少一个数量级,尽管显然需要手动实现,而不是简单得多的
    BinaryFormatter
    方法。对于深度对象图,差异变得更加明显

    最后,我们决定采取三管齐下的“克隆”方法:

    • 如果类型是不可变的(我们将使用标记接口,
      IImutable
      ,但您同样可以使用属性或类似的东西来表示),我们将通过返回原始实例来“克隆”。因为我们知道没有人可以变异它,所以继续返回相同的实例是安全的。很明显,这是最快的“克隆”,尽管显然根本不是真正的克隆
    • 如果该类型实现了我们自己的
      IDeepCloneable
      接口(类似于您的第二个示例,但是是通用的),我们会使用它。[此通用接口将继承非通用等价物
      IDeepCloneable
      ]
    • 如果做不到这一点,我们将回到您的第一个示例,
      BinaryFormatter

    我之所以提到“不可变”方法,是因为根据您正在做的事情,有时候最好的方法是重新设计需要克隆的类,这样它们就根本不需要被克隆。如果它们一经创建就基本上是只读的,那么这很容易;但即使不是,有时构建器/不可变类型方法也是有用的(请参见框架中的
    Uri
    UriBuilder
    。前者基本上是不可变的,而后者可用于构建和/或变异前者的实例)。

    MemberwiseClone不使用反射吗?或者它只是一个memcpy,而GC不需要任何进一步的帮助?我认为它使用了一些CLR钩子来获得性能,所以基本上是memcpy和一些调整。正如MemberwiseClone的帮助中提到的那样,它制作了一个浅层拷贝,这可能与使用BinaryFormatter相同吗?我对此表示怀疑,因为该函数名为DeepCopy,我猜它的名称不正确。我的观点是,深度克隆也应该克隆引用的对象???@Sam-此方法是通用的,并在其他地方使用,但如果存在嵌套对象,则会将其克隆为well@Sam,OP在标题中指定了DeepCopy,但随后提到他只有浅层对象要复制,所以MemberwiseClone()比BinaryFormatter高效得多。我不知道为什么要标记此DeepClone(),而您只想执行浅层复制。因为字符串是不可变的,所以字符串的浅拷贝相当于深拷贝。此外,手动设置必须快得多,因为BinaryFormatter使用反射获取属性名、setter、构造函数等@Gelno-如果对象中有嵌套对象(因此名称),此方法将深度克隆对象
    class A : ICloneable
    {
        private readonly int _member;
    
        public A(int member)
        {
            _member = member;
        }
    
        public A(A a)
        {
            _member = a._member;
        }
    
        public object Clone()
        {
            return new A(this);
        }
    }
    
    class A : ICloneable
    {
        private readonly int _member;
    
        public A(int member)
        {
            _member = member;
        }
    
        public object Clone()
        {
            return MemberwiseClone();
        }
    }