代码之家  ›  专栏  ›  技术社区  ›  NakedBrunch

深度克隆对象

  •  1974
  • NakedBrunch  · 技术社区  · 17 年前

    我想做如下的事情:

    MyObject myObj = GetMyObj(); // Create and fill a new object
    MyObject newObj = myObj.Clone();
    

    然后对未反映在原始对象中的新对象进行更改。

    我通常不需要这个功能,所以当需要时,我会先创建一个新的对象,然后分别复制每个属性,但它总是让我觉得有更好或更优雅的方式来处理这种情况。

    我如何克隆或深度复制一个对象,以便可以修改克隆的对象而不在原始对象中反映任何更改?

    41 回复  |  直到 7 年前
        1
  •  1556
  •   community wiki 19 revs, 12 users 65% johnc    9 年前

    而标准做法是实施 ICloneable 接口(描述 here ,所以我不会反胃),这是一个很好的深克隆物体复印机,我在上面找到的。 The Code Project 一段时间前,把它融入我们的东西。

    正如在别处提到的,它确实要求您的对象是可序列化的。

    using System;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    
    /// <summary>
    /// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// Provides a method for performing a deep copy of an object.
    /// Binary Serialization is used to perform the copy.
    /// </summary>
    public static class ObjectCopier
    {
        /// <summary>
        /// Perform a deep Copy of the object.
        /// </summary>
        /// <typeparam name="T">The type of object being copied.</typeparam>
        /// <param name="source">The object instance to copy.</param>
        /// <returns>The copied object.</returns>
        public static T Clone<T>(T source)
        {
            if (!typeof(T).IsSerializable)
            {
                throw new ArgumentException("The type must be serializable.", "source");
            }
    
            // Don't serialize a null object, simply return the default for that object
            if (Object.ReferenceEquals(source, null))
            {
                return default(T);
            }
    
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            using (stream)
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)formatter.Deserialize(stream);
            }
        }
    }
    

    其思想是将对象序列化,然后将其反序列化为新对象。好处是,当对象变得过于复杂时,您不必担心克隆任何东西。

    以及使用扩展方法(也来自最初引用的源):

    如果你喜欢用新的 extension methods 对于C 3.0,将方法更改为具有以下签名:

    public static T Clone<T>(this T source)
    {
       //...
    }
    

    现在方法调用简单地变成 objectBeingCloned.Clone(); .

    编辑 (2015年1月10日)我想重新审视一下这个问题,更重要的是我最近开始使用(newtonsoft)json来做这个,它 should be 更轻,并且避免了[serializable]标记的开销。( @atconway在评论中指出私有成员不是使用json方法克隆的)

    /// <summary>
    /// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneJson<T>(this T source)
    {            
        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }
    
        // initialize inner objects individually
        // for example in default constructor some list property initialized with some values,
        // but in 'source' these items are cleaned -
        // without ObjectCreationHandling.Replace default constructor values will be added to result
        var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};
    
        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
    }
    
        2
  •  217
  •   Vineet Choudhary    7 年前

    我想要一个克隆器来克隆非常简单的对象,其中大部分都是原语和列表。如果您的对象是开箱即用的可序列化JSON,那么这个方法就可以做到这一点。这不需要修改或实现克隆类上的接口,只需要像json.net这样的JSON序列化程序。

    public static T Clone<T>(T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
    

    另外,您可以使用这个扩展方法

    public static class SystemExtension
    {
        public static T Clone<T>(this T source)
        {
            var serialized = JsonConvert.SerializeObject(source);
            return JsonConvert.DeserializeObject<T>(serialized);
        }
    }
    
        3
  •  161
  •   Ryan Lundy    10 年前

    不使用的原因 ICloneable 因为它没有通用接口。 The reason not to use it is because it's vague . 它不清楚您得到的是一个浅薄的还是一个深刻的副本;这取决于实现者。

    对, MemberwiseClone 做一个浅显的复制,但与 分贝酮 不是 Clone 可能是, DeepClone 不存在。当通过对象的ICloneable接口使用对象时,您不知道底层对象执行的是哪种克隆。(而XML注释并不能说明这一点,因为您将得到接口注释,而不是对象克隆方法上的注释。)

    我通常做的就是 Copy 方法可以完全满足我的需要。

        4
  •  99
  •   Michael Freidgeim    8 年前

    我相信,在阅读了大量关于这里所链接的许多选项以及这个问题的可能解决方案的文章之后, all the options are summarized pretty well at Ian P 's link (所有其他选项都是这些选项的变体)最佳解决方案由 Pedro77 's link 关于问题的评论。

    所以我将在这里复制这两个参考文献的相关部分。这样我们就可以:

    在C夏普中克隆对象的最佳方法!

    首先,这些都是我们的选择:

    这个 article Fast Deep Copy by Expression Trees 通过序列树、反射树和表达式树对克隆的性能进行了比较。

    为什么我选择 可逗留的 (即手动)

    Mr Venkat Subramaniam (redundant link here) explains in much detail why .

    他的所有文章都围绕着一个试图适用于大多数情况的示例,使用3个对象: , 西蒂 . 我们想克隆一个人,他将有自己的大脑,但同样的城市。您可以想象所有的问题,上面的任何其他方法都可以带来或阅读这篇文章。

    这是我对他的结论稍加修改的版本:

    通过指定 New 后面跟着类名通常会导致代码不可扩展。使用克隆,原型模式的应用,是实现这一点的更好方法。然而,使用克隆(如C语言)(和Java)提供的问题也相当大。最好提供一个受保护(非公共)的复制构造函数并从clone方法调用它。这使我们能够将创建对象的任务委托给类本身的实例,从而提供可扩展性,并使用受保护的复制构造函数安全地创建对象。

    希望这个实现可以使事情变得清楚:

    public class Person : ICloneable
    {
        private final Brain brain; // brain is final since I do not want 
                    // any transplant on it once created!
        private int age;
        public Person(Brain aBrain, int theAge)
        {
            brain = aBrain; 
            age = theAge;
        }
        protected Person(Person another)
        {
            Brain refBrain = null;
            try
            {
                refBrain = (Brain) another.brain.clone();
                // You can set the brain in the constructor
            }
            catch(CloneNotSupportedException e) {}
            brain = refBrain;
            age = another.age;
        }
        public String toString()
        {
            return "This is person with " + brain;
            // Not meant to sound rude as it reads!
        }
        public Object clone()
        {
            return new Person(this);
        }
        …
    }
    

    现在考虑让一个类从人派生。

    public class SkilledPerson extends Person
    {
        private String theSkills;
        public SkilledPerson(Brain aBrain, int theAge, String skills)
        {
            super(aBrain, theAge);
            theSkills = skills;
        }
        protected SkilledPerson(SkilledPerson another)
        {
            super(another);
            theSkills = another.theSkills;
        }
    
        public Object clone()
        {
            return new SkilledPerson(this);
        }
        public String toString()
        {
            return "SkilledPerson: " + super.toString();
        }
    }
    

    您可以尝试运行以下代码:

    public class User
    {
        public static void play(Person p)
        {
            Person another = (Person) p.clone();
            System.out.println(p);
            System.out.println(another);
        }
        public static void main(String[] args)
        {
            Person sam = new Person(new Brain(), 1);
            play(sam);
            SkilledPerson bob = new SkilledPerson(new SmarterBrain(), 1, "Writer");
            play(bob);
        }
    }
    

    产生的输出将是:

    This is person with Brain@1fcc69
    This is person with Brain@253498
    SkilledPerson: This is person with SmarterBrain@1fef6f
    SkilledPerson: This is person with SmarterBrain@209f4e
    

    注意,如果我们对对象的数量进行计数,这里实现的克隆将对对象的数量进行正确的计数。

        5
  •  76
  •   Peter Mortensen Pieter Jan Bonestroo    13 年前

    比起克隆,我更喜欢复制构造函数。目的更加明确。

        6
  •  38
  •   SanyTiger Arkadas Kilic    10 年前

    复制所有公共属性的简单扩展方法。适用于任何对象和 要求类为 [Serializable] . 可以扩展到其他访问级别。

    public static void CopyTo( this object S, object T )
    {
        foreach( var pS in S.GetType().GetProperties() )
        {
            foreach( var pT in T.GetType().GetProperties() )
            {
                if( pT.Name != pS.Name ) continue;
                ( pT.GetSetMethod() ).Invoke( T, new object[] 
                { pS.GetGetMethod().Invoke( S, null ) } );
            }
        };
    }
    
        7
  •  30
  •   Hakan Fıstık MuriloKunze    9 年前

    我在Silverlight中使用icloneable时遇到了一些问题,但是我喜欢seravization的思想,我可以将XML seravizing,所以我这样做了:

    static public class SerializeHelper
    {
        //Michael White, Holly Springs Consulting, 2009
        //michael@hollyspringsconsulting.com
        public static T DeserializeXML<T>(string xmlData) where T:new()
        {
            if (string.IsNullOrEmpty(xmlData))
                return default(T);
    
            TextReader tr = new StringReader(xmlData);
            T DocItms = new T();
            XmlSerializer xms = new XmlSerializer(DocItms.GetType());
            DocItms = (T)xms.Deserialize(tr);
    
            return DocItms == null ? default(T) : DocItms;
        }
    
        public static string SeralizeObjectToXML<T>(T xmlObject)
        {
            StringBuilder sbTR = new StringBuilder();
            XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
            XmlWriterSettings xwsTR = new XmlWriterSettings();
    
            XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
            xmsTR.Serialize(xmwTR,xmlObject);
    
            return sbTR.ToString();
        }
    
        public static T CloneObject<T>(T objClone) where T:new()
        {
            string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
            return SerializeHelper.DeserializeXML<T>(GetString);
        }
    }
    
        8
  •  26
  •   MarcinJuraszek    10 年前

    我刚刚创造了 CloneExtensions library 项目。它使用表达式树运行时代码编译生成的简单分配操作执行快速、深入的克隆。

    如何使用?

    而不是自己写 Clone Copy 方法使用表达式树,在字段和属性之间使用一种指定的音调,让程序自己完成它。 GetClone<T>() 标记为扩展方法的方法允许您在实例上简单地调用它:

    var newInstance = source.GetClone();
    

    您可以选择要从中复制的内容 source newInstance 使用 CloningFlags 枚举:

    var newInstance 
        = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
    

    什么可以克隆?

    • 原语(int、uint、byte、double、char等),已知不可变 类型(日期时间、时间跨度、字符串)和委托(包括 动作、功能等)
    • 可空的
    • t[]数组
    • 自定义类和结构,包括泛型类和结构。

    以下类/结构成员在内部克隆:

    • 公共字段的值,而不是只读字段的值
    • 带有get和set访问器的公共属性值
    • 实现ICollection的类型的集合项

    它有多快?

    解决方案比反射更快,因为成员信息必须在 GetClone<T> 第一次用于给定类型 T .

    当克隆多个相同类型的实例时,它也比基于序列化的解决方案更快。 T .

    还有更多…

    阅读有关上生成的表达式的详细信息 documentation .

    示例表达式调试列表 List<int> :

    .Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
        System.Collections.Generic.List`1[System.Int32] $source,
        CloneExtensions.CloningFlags $flags,
        System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
        .Block(System.Collections.Generic.List`1[System.Int32] $target) {
            .If ($source == null) {
                .Return #Label1 { null }
            } .Else {
                .Default(System.Void)
            };
            .If (
                .Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
            ) {
                $target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
                ).Invoke((System.Object)$source)
            } .Else {
                $target = .New System.Collections.Generic.List`1[System.Int32]()
            };
            .If (
                ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
            ) {
                .Default(System.Void)
            } .Else {
                .Default(System.Void)
            };
            .If (
                ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
            ) {
                .Block() {
                    $target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
                        $source.Capacity,
                        $flags,
                        $initializers)
                }
            } .Else {
                .Default(System.Void)
            };
            .If (
                ((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
            ) {
                .Block(
                    System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
                    System.Collections.Generic.ICollection`1[System.Int32] $var2) {
                    $var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
                    $var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
                    .Loop  {
                        .If (.Call $var1.MoveNext() != False) {
                            .Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
                                    $var1.Current,
                                    $flags,
    
    
                             $initializers))
                    } .Else {
                        .Break #Label2 { }
                    }
                }
                .LabelTarget #Label2:
            }
        } .Else {
            .Default(System.Void)
        };
        .Label
            $target
        .LabelTarget #Label1:
    }
    

    }

    以下C代码的含义相同:

    (source, flags, initializers) =>
    {
        if(source == null)
            return null;
    
        if(initializers.ContainsKey(typeof(List<int>))
            target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
        else
            target = new List<int>();
    
        if((flags & CloningFlags.Properties) == CloningFlags.Properties)
        {
            target.Capacity = target.Capacity.GetClone(flags, initializers);
        }
    
        if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
        {
            var targetCollection = (ICollection<int>)target;
            foreach(var item in (ICollection<int>)source)
            {
                targetCollection.Add(item.Clone(flags, initializers));
            }
        }
    
        return target;
    }
    

    这不是很像你自己写的吗 克隆 方法 清单& lt;int & gt; ?

        9
  •  26
  •   Stacked Emily M    9 年前

    如果您已经在使用第三方应用程序,例如 ValueInjecter Automapper ,您可以这样做:

    MyObject oldObj; // The existing object to clone
    
    MyObject newObj = new MyObject();
    newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
    

    使用此方法,您不必在对象上实现ISerializable或IClonable。这在MVC/MVVM模式中很常见,因此已经创建了类似这样的简单工具。

    看见 the valueinjecter deep cloning solution on CodePlex .

        10
  •  20
  •   Johann Chris Greenough    8 年前

    简短的回答是,您从ICloneable接口继承,然后实现.clone函数。克隆应执行memberwise复制,并对任何需要它的成员执行深度复制,然后返回结果对象。这是一个递归操作(它要求要克隆的类的所有成员要么是值类型,要么是实现ICloneable,并且它们的成员要么是值类型,要么是实现ICloneable,依此类推)。

    有关使用icloneable克隆的详细说明,请查看 this article .

    这个 长的 答案是“视情况而定”。正如其他人所提到的,icloneable不受泛型的支持,需要对循环类引用进行特殊考虑,并且实际上被一些人视为 "mistake" 在.NET框架中。序列化方法取决于要序列化的对象,这些对象可能不是,您可能无法控制。在社区中,关于“最佳”实践的争论仍然很多。实际上,没有一个解决方案是适用于所有情况的“一刀切”最佳实践,就像最初解释的icloneable那样。

    看到这个 Developer's Corner article 为了更多的选择(归功于伊恩)。

        11
  •  17
  •   Community Mohan Dere    9 年前

    最好是实现 扩展方法 喜欢

    public static T DeepClone<T>(this T originalObject)
    { /* the cloning code */ }
    

    然后在解决方案中的任何位置使用它

    var copy = anyObject.DeepClone();
    

    我们可以有以下三种实现:

    1. By Serialization (最短代码)
    2. By Reflection - 快5倍
    3. By Expression Trees - 快20X

    所有的连接方法都工作良好,并经过了深入的测试。

        12
  •  16
  •   Christian Davén    9 年前
    1. 基本上需要实现ICloneable接口,然后实现对象结构复制。
    2. 如果它是所有成员的深度副本,那么您需要确保(与您选择的解决方案无关)所有的孩子也都是可克隆的。
    3. 在这个过程中,有时您需要注意一些限制,例如,如果您复制ORM对象,大多数框架只允许一个对象附加到会话,并且您不能复制这个对象,或者如果可能的话,您需要关心这些对象的会话附加。

    干杯。

        13
  •  15
  •   Michael Sander    10 年前

    如果你想真正克隆到未知类型,你可以看看 fastclone .

    这是基于表达式的克隆,其工作速度比二进制序列化快10倍,并且保持了完整的对象图完整性。

    这意味着:如果在层次结构中多次引用同一对象,克隆也将引用一个实例。

    不需要对要克隆的对象进行接口、属性或任何其他修改。

        14
  •  11
  •   Stacked Emily M    10 年前

    简单易用 AutoMapper 正如其他人提到的,它是一个简单的小库,可以将一个对象映射到另一个对象…要将一个对象复制到另一个具有相同类型的对象,只需要三行代码:

    MyType source = new MyType();
    Mapper.CreateMap<MyType, MyType>();
    MyType target = Mapper.Map<MyType, MyType>(source);
    

    目标对象现在是源对象的副本。 不够简单?创建扩展方法以在解决方案中的任何位置使用:

    public static T Copy<T>(this T source)
    {
        T copy = default(T);
        Mapper.CreateMap<T, T>();
        copy = Mapper.Map<T, T>(source);
        return copy;
    }
    

    通过使用扩展方法,三条线变为一条线:

    MyType copy = source.Copy();
    
        15
  •  10
  •   Peter Mortensen Pieter Jan Bonestroo    13 年前

    我想出这个来克服 .NET 缺点是必须手动深度复制列表<t>。

    我用这个:

    static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
    {
        foreach (SpotPlacement sp in spotPlacements)
        {
            yield return (SpotPlacement)sp.Clone();
        }
    }
    

    在另一个地方:

    public object Clone()
    {
        OrderItem newOrderItem = new OrderItem();
        ...
        newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
        ...
        return newOrderItem;
    }
    

    我尝试使用一个行程序来实现这一点,但这是不可能的,因为在匿名方法块中不工作。

    更好的是,使用通用列表<t>克隆器:

    class Utility<T> where T : ICloneable
    {
        static public IEnumerable<T> CloneList(List<T> tl)
        {
            foreach (T t in tl)
            {
                yield return (T)t.Clone();
            }
        }
    }
    
        16
  •  7
  •   HappyDude    17 年前

    通常,您实现ICloneable接口并自己实现克隆。 C对象有一个内置的memberWiseClone方法,它执行一个浅拷贝,可以帮助您完成所有原语。

    对于深度复制,它不可能知道如何自动执行。

        17
  •  7
  •   xr280xr    15 年前

    我也看到它是通过反射实现的。基本上,有一个方法可以迭代对象的成员,并将它们适当地复制到新对象。当它到达引用类型或集合时,我认为它对自身进行了递归调用。反射是昂贵的,但效果很好。

        18
  •  7
  •   dougajmcdonald    14 年前

    下面是一个深度复制实现:

    public static object CloneObject(object opSource)
    {
        //grab the type and create a new instance of that type
        Type opSourceType = opSource.GetType();
        object opTarget = CreateInstanceOfType(opSourceType);
    
        //grab the properties
        PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    
        //iterate over the properties and if it has a 'set' method assign it from the source TO the target
        foreach (PropertyInfo item in opPropertyInfo)
        {
            if (item.CanWrite)
            {
                //value types can simply be 'set'
                if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
                {
                    item.SetValue(opTarget, item.GetValue(opSource, null), null);
                }
                //object/complex types need to recursively call this method until the end of the tree is reached
                else
                {
                    object opPropertyValue = item.GetValue(opSource, null);
                    if (opPropertyValue == null)
                    {
                        item.SetValue(opTarget, null, null);
                    }
                    else
                    {
                        item.SetValue(opTarget, CloneObject(opPropertyValue), null);
                    }
                }
            }
        }
        //return the new item
        return opTarget;
    }
    
        19
  •  7
  •   Contango    10 年前

    问:我为什么要选择这个答案?

    • 如果您希望.NET能够提供最快的速度,请选择此答案。
    • 如果你想要一个真正简单的克隆方法,忽略这个答案。

    换言之, go with another answer unless you have a performance bottleneck that needs fixing, and you can prove it with a profiler .

    比其他方法快10倍

    执行深度克隆的以下方法是:

    • 比任何涉及序列化/反序列化的操作都快10倍;
    • 非常接近理论上的最大速度。

    方法……

    对于终极速度,您可以使用 嵌套的MemberWiseClone执行深度复制 . 它几乎与复制值结构的速度相同,并且比(a)反射或(b)序列化(如本页其他答案中所述)快得多。

    注意 如果 你用 用于深度复制的嵌套MemberWiseClone ,您必须为类中的每个嵌套级别手动实现一个Shallowcopy,以及一个调用所有上述Shallowcopy方法以创建完整克隆的deepcopy。这很简单:总共只有几行,请参见下面的演示代码。

    下面是显示100000个克隆的相对性能差异的代码输出:

    • 嵌套结构上的嵌套MemberWiseClone为1.08秒
    • 嵌套类上的嵌套MemberWiseClone为4.77秒
    • 39.93秒用于序列化/反序列化

    在类上使用嵌套的MemberWiseClone的速度几乎与复制结构的速度相同,并且复制结构的速度非常接近理论上.NET所能达到的最大速度。

    Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
      Create Bob
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Clone Bob >> BobsSon
      Adjust BobsSon details
        BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
      Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Elapsed time: 00:00:04.7795670,30000000
    
    Demo 2 of shallow and deep copy, using structs and value copying:
      Create Bob
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Clone Bob >> BobsSon
      Adjust BobsSon details:
        BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
      Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Elapsed time: 00:00:01.0875454,30000000
    
    Demo 3 of deep copy, using class and serialize/deserialize:
      Elapsed time: 00:00:39.9339425,30000000
    

    要了解如何使用memberwisecopy进行深度复制,下面是用于生成上述时间的演示项目:

    // Nested MemberwiseClone example. 
    // Added to demo how to deep copy a reference class.
    [Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
    public class Person
    {
        public Person(int age, string description)
        {
            this.Age = age;
            this.Purchase.Description = description;
        }
        [Serializable] // Not required if using MemberwiseClone
        public class PurchaseType
        {
            public string Description;
            public PurchaseType ShallowCopy()
            {
                return (PurchaseType)this.MemberwiseClone();
            }
        }
        public PurchaseType Purchase = new PurchaseType();
        public int Age;
        // Add this if using nested MemberwiseClone.
        // This is a class, which is a reference type, so cloning is more difficult.
        public Person ShallowCopy()
        {
            return (Person)this.MemberwiseClone();
        }
        // Add this if using nested MemberwiseClone.
        // This is a class, which is a reference type, so cloning is more difficult.
        public Person DeepCopy()
        {
                // Clone the root ...
            Person other = (Person) this.MemberwiseClone();
                // ... then clone the nested class.
            other.Purchase = this.Purchase.ShallowCopy();
            return other;
        }
    }
    // Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
    public struct PersonStruct
    {
        public PersonStruct(int age, string description)
        {
            this.Age = age;
            this.Purchase.Description = description;
        }
        public struct PurchaseType
        {
            public string Description;
        }
        public PurchaseType Purchase;
        public int Age;
        // This is a struct, which is a value type, so everything is a clone by default.
        public PersonStruct ShallowCopy()
        {
            return (PersonStruct)this;
        }
        // This is a struct, which is a value type, so everything is a clone by default.
        public PersonStruct DeepCopy()
        {
            return (PersonStruct)this;
        }
    }
    // Added only for a speed comparison.
    public class MyDeepCopy
    {
        public static T DeepCopy<T>(T obj)
        {
            object result = null;
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                ms.Position = 0;
                result = (T)formatter.Deserialize(ms);
                ms.Close();
            }
            return (T)result;
        }
    }
    

    然后,从MAIN调用演示:

    void MyMain(string[] args)
    {
        {
            Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
            var Bob = new Person(30, "Lamborghini");
            Console.Write("  Create Bob\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
            Console.Write("  Clone Bob >> BobsSon\n");
            var BobsSon = Bob.DeepCopy();
            Console.Write("  Adjust BobsSon details\n");
            BobsSon.Age = 2;
            BobsSon.Purchase.Description = "Toy car";
            Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
            Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
            Debug.Assert(Bob.Age == 30);
            Debug.Assert(Bob.Purchase.Description == "Lamborghini");
            var sw = new Stopwatch();
            sw.Start();
            int total = 0;
            for (int i = 0; i < 100000; i++)
            {
                var n = Bob.DeepCopy();
                total += n.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
        }
        {               
            Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
            var Bob = new PersonStruct(30, "Lamborghini");
            Console.Write("  Create Bob\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
            Console.Write("  Clone Bob >> BobsSon\n");
            var BobsSon = Bob.DeepCopy();
            Console.Write("  Adjust BobsSon details:\n");
            BobsSon.Age = 2;
            BobsSon.Purchase.Description = "Toy car";
            Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
            Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);                
            Debug.Assert(Bob.Age == 30);
            Debug.Assert(Bob.Purchase.Description == "Lamborghini");
            var sw = new Stopwatch();
            sw.Start();
            int total = 0;
            for (int i = 0; i < 100000; i++)
            {
                var n = Bob.DeepCopy();
                total += n.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
        }
        {
            Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
            int total = 0;
            var sw = new Stopwatch();
            sw.Start();
            var Bob = new Person(30, "Lamborghini");
            for (int i = 0; i < 100000; i++)
            {
                var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
                total += BobsSon.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
        }
        Console.ReadKey();
    }
    

    同样,请注意 如果 你用 用于深度复制的嵌套MemberWiseClone ,您必须为类中的每个嵌套级别手动实现一个Shallowcopy,以及一个调用所有上述Shallowcopy方法以创建完整克隆的deepcopy。这很简单:总共只有几行,请参见上面的演示代码。

    值类型与引用类型

    请注意,在克隆对象时,在 结构 “和” “:

    • 如果你有一个“ 结构 “这是一个 价值类型 所以你只需复制它,内容就会被克隆(但除非你使用本文中的技术,否则它只会进行一个浅层的克隆)。
    • 如果你有一个“ “这是一个 引用类型 如果你复制它,你所要做的就是复制指向它的指针。要创建一个真正的克隆,你必须更具创造性,并使用 differences between value types and references types 它在内存中创建原始对象的另一个副本。

    值类型和引用类型之间的差异 .

    帮助调试的校验和

    • 不正确地克隆对象会导致很难找出错误。在生产代码中,我倾向于实现一个校验和来复查对象是否被正确克隆,并且没有被对它的另一个引用破坏。此校验和可在释放模式下关闭。
    • 我发现这个方法非常有用:通常,您只想复制对象的部分,而不是整个对象。

    对于将多个线程与许多其他线程分离非常有用

    此代码的一个很好的用例是将嵌套类或结构的克隆提供到队列中,以实现生产者/使用者模式。

    • 我们可以让一个(或多个)线程修改自己的类,然后将该类的完整副本推送到 ConcurrentQueue .
    • 然后我们有一个(或多个)线程将这些类的副本拉出并处理它们。

    这在实践中非常有效,并且允许我们将多个线程(生产者)与一个或多个线程(消费者)分离。

    这个方法也非常快:如果我们使用嵌套结构,它比序列化/反序列化嵌套类快35倍,并且允许我们利用机器上所有可用的线程。

    更新

    显然,ExpressMapper的速度(如果不是更快的话)与上面的手工编码一样快。我可能需要看看它们如何与分析器进行比较。

        20
  •  7
  •   kalisohn    10 年前

    由于在不同的项目中找不到满足我所有需求的克隆器,所以我创建了一个深度克隆器,它可以配置和适应不同的代码结构,而不是修改我的代码来满足克隆器的需求。它是通过在应该克隆的代码中添加注释来实现的,或者您只需保持代码的默认行为。它使用反射、类型缓存并基于 fasterflect . 对于大量数据和高对象层次结构(与其他基于反射/序列化的算法相比),克隆过程非常快。

    https://github.com/kalisohn/CloneBehave

    也可作为Nuget软件包提供: https://www.nuget.org/packages/Clone.Behave/1.0.0

    例如:下面的代码将DEEPCLONE ADDRESS,但只执行_currentJob字段的浅拷贝。

    public class Person 
    {
      [DeepClone(DeepCloneBehavior.Shallow)]
      private Job _currentJob;      
    
      public string Name { get; set; }
    
      public Job CurrentJob 
      { 
        get{ return _currentJob; }
        set{ _currentJob = value; }
      }
    
      public Person Manager { get; set; }
    }
    
    public class Address 
    {      
      public Person PersonLivingHere { get; set; }
    }
    
    Address adr = new Address();
    adr.PersonLivingHere = new Person("John");
    adr.PersonLivingHere.BestFriend = new Person("James");
    adr.PersonLivingHere.CurrentJob = new Job("Programmer");
    
    Address adrClone = adr.Clone();
    
    //RESULT
    adr.PersonLivingHere == adrClone.PersonLivingHere //false
    adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
    adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
    adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true
    
        21
  •  6
  •   GorvGoyl    10 年前

    这种方法解决了我的问题:

    private static MyObj DeepCopy(MyObj source)
            {
    
                var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
    
                return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);
    
            }
    

    这样使用: MyObj a = DeepCopy(b);

        22
  •  5
  •   LuckyLikey    11 年前

    我喜欢这样的复制构造器:

        public AnyObject(AnyObject anyObject)
        {
            foreach (var property in typeof(AnyObject).GetProperties())
            {
                property.SetValue(this, property.GetValue(anyObject));
            }
            foreach (var field in typeof(AnyObject).GetFields())
            {
                field.SetValue(this, field.GetValue(anyObject));
            }
        }
    

    如果有更多的东西要复制,请添加它们

        23
  •  5
  •   Toxantron    10 年前

    代码生成器

    我们已经看到了很多想法,从手工实现的序列化到反思,我想提出一种完全不同的方法,使用 CGbR Code Generator . generate clone方法具有内存和CPU效率,因此比标准的DataContractSerializer快300倍。

    你只需要一个部分类定义 ICloneable 发电机完成剩下的工作:

    public partial class Root : ICloneable
    {
        public Root(int number)
        {
            _number = number;
        }
        private int _number;
    
        public Partial[] Partials { get; set; }
    
        public IList<ulong> Numbers { get; set; }
    
        public object Clone()
        {
            return Clone(true);
        }
    
        private Root()
        {
        }
    } 
    
    public partial class Root
    {
        public Root Clone(bool deep)
        {
            var copy = new Root();
            // All value types can be simply copied
            copy._number = _number; 
            if (deep)
            {
                // In a deep clone the references are cloned 
                var tempPartials = new Partial[Partials.Length];
                for (var i = 0; i < Partials.Length; i++)
                {
                    var value = Partials[i];
                    value = value.Clone(true);
                    tempPartials[i] = value;
                }
                copy.Partials = tempPartials;
                var tempNumbers = new List<ulong>(Numbers.Count);
                for (var i = 0; i < Numbers.Count; i++)
                {
                    var value = Numbers[i];
                    tempNumbers.Add(value);
                }
                copy.Numbers = tempNumbers;
            }
            else
            {
                // In a shallow clone only references are copied
                copy.Partials = Partials; 
                copy.Numbers = Numbers; 
            }
            return copy;
        }
    }
    

    注: 最新的版本有更多的空检查,但为了更好地理解,我省略了它们。

        24
  •  5
  •   Daniele D.    9 年前

    这里有一个快速而简单的解决方案,它在不中继序列化/反序列化的情况下为我工作。

    public class MyClass
    {
        public virtual MyClass DeepClone()
        {
            var returnObj = (MyClass)MemberwiseClone();
            var type = returnObj.GetType();
            var fieldInfoArray = type.GetRuntimeFields().ToArray();
    
            foreach (var fieldInfo in fieldInfoArray)
            {
                object sourceFieldValue = fieldInfo.GetValue(this);
                if (!(sourceFieldValue is MyClass))
                {
                    continue;
                }
    
                var sourceObj = (MyClass)sourceFieldValue;
                var clonedObj = sourceObj.DeepClone();
                fieldInfo.SetValue(returnObj, clonedObj);
            }
            return returnObj;
        }
    }
    

    编辑 : 要求

        using System.Linq;
        using System.Reflection;
    

    我就是这么用的

    public MyClass Clone(MyClass theObjectIneededToClone)
    {
        MyClass clonedObj = theObjectIneededToClone.DeepClone();
    }
    
        25
  •  4
  •   Mifeet Alexander    13 年前

    遵循以下步骤:

    • 定义一个 ISelf<T> 具有只读 Self 返回的属性 T ICloneable<out T> ,源自 Ilt & lt;t & gt; 包括一种方法 T Clone() .
    • 然后定义一个 CloneBase 实现的类型 protected virtual generic VirtualClone 铸造 MemberwiseClone 传入的类型。
    • 每个派生类型都应实现 VirtualClone 通过调用基本克隆方法,然后执行需要执行的任何操作来正确克隆父virtualclone方法尚未处理的派生类型的那些方面。

    为了获得最大的继承多功能性,公开公共克隆功能的类应该是 sealed ,但派生自一个基本类,该类在其他方面是相同的,除非缺少克隆。不传递显式可克隆类型的变量,而是采用类型的参数 ICloneable<theNonCloneableType> . 这将允许一个例程期望 Foo 使用可克隆的 DerivedFoo 同时也允许产生不可克隆的 .

        26
  •  4
  •   Sudhanva Kotabagi    9 年前

    我想你可以试试这个。

    MyObject myObj = GetMyObj(); // Create and fill a new object
    MyObject newObj = new MyObject(myObj); //DeepClone it
    
        27
  •  3
  •   Jeroen Ritmeijer    12 年前

    我已经创建了一个可与'[serializable]'和'[datacontract]'同时使用的接受回答版本。我写它已经有一段时间了,但是如果我正确地记得[datacontract]需要一个不同的序列化程序。

    要求 系统,system.io,system.runtime.serialization,system.runtime.serialization.formatters.binary,system.xml ;

    public static class ObjectCopier
    {
    
        /// <summary>
        /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
        /// </summary>
        /// <typeparam name="T">The type of object being copied.</typeparam>
        /// <param name="source">The object instance to copy.</param>
        /// <returns>The copied object.</returns>
        public static T Clone<T>(T source)
        {
            if (typeof(T).IsSerializable == true)
            {
                return CloneUsingSerializable<T>(source);
            }
    
            if (IsDataContract(typeof(T)) == true)
            {
                return CloneUsingDataContracts<T>(source);
            }
    
            throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
        }
    
    
        /// <summary>
        /// Perform a deep Copy of an object that is marked with '[Serializable]'
        /// </summary>
        /// <remarks>
        /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
        /// Uses code found on CodeProject, which allows free use in third party apps
        /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
        /// </remarks>
        /// <typeparam name="T">The type of object being copied.</typeparam>
        /// <param name="source">The object instance to copy.</param>
        /// <returns>The copied object.</returns>
        public static T CloneUsingSerializable<T>(T source)
        {
            if (!typeof(T).IsSerializable)
            {
                throw new ArgumentException("The type must be serializable.", "source");
            }
    
            // Don't serialize a null object, simply return the default for that object
            if (Object.ReferenceEquals(source, null))
            {
                return default(T);
            }
    
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            using (stream)
            {
                formatter.Serialize(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)formatter.Deserialize(stream);
            }
        }
    
    
        /// <summary>
        /// Perform a deep Copy of an object that is marked with '[DataContract]'
        /// </summary>
        /// <typeparam name="T">The type of object being copied.</typeparam>
        /// <param name="source">The object instance to copy.</param>
        /// <returns>The copied object.</returns>
        public static T CloneUsingDataContracts<T>(T source)
        {
            if (IsDataContract(typeof(T)) == false)
            {
                throw new ArgumentException("The type must be a data contract.", "source");
            }
    
            // ** Don't serialize a null object, simply return the default for that object
            if (Object.ReferenceEquals(source, null))
            {
                return default(T);
            }
    
            DataContractSerializer dcs = new DataContractSerializer(typeof(T));
            using(Stream stream = new MemoryStream())
            {
                using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
                {
                    dcs.WriteObject(writer, source);
                    writer.Flush();
                    stream.Seek(0, SeekOrigin.Begin);
                    using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                    {
                        return (T)dcs.ReadObject(reader);
                    }
                }
            }
        }
    
    
        /// <summary>
        /// Helper function to check if a class is a [DataContract]
        /// </summary>
        /// <param name="type">The type of the object to check.</param>
        /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
        public static bool IsDataContract(Type type)
        {
            object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
            return attributes.Length == 1;
        }
    
    } 
    
        28
  •  3
  •   Chtioui Malek    12 年前

    要克隆类对象,可以使用object.memberWiseClone方法,

    只需将此函数添加到类中:

    public class yourClass
    {
        // ...
        // ...
    
        public yourClass DeepCopy()
        {
            yourClass othercopy = (yourClass)this.MemberwiseClone();
            return othercopy;
        }
    }
    

    然后,要执行深度独立复制,只需调用deep copy方法:

    yourClass newLine = oldLine.DeepCopy();
    

    希望这有帮助。

        29
  •  3
  •   Community Mohan Dere    9 年前

    好的,这篇文章中有一些关于反射的明显例子,但是反射通常很慢,直到您开始正确地缓存它。

    如果您能正确地缓存它,那么它将以4,6秒(由观察者测量)深度克隆1000000个对象。

    static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
    

    而不是将缓存属性或新属性添加到字典并简单地使用它们

    foreach (var prop in propList)
    {
            var value = prop.GetValue(source, null);   
            prop.SetValue(copyInstance, value, null);
    }
    

    完整的代码在我的帖子中签入另一个答案

    https://stackoverflow.com/a/34365709/4711853

        30
  •  3
  •   Hakan Fıstık MuriloKunze    9 年前

    如果对象树是可序列化的,则也可以使用类似的内容

    static public MyClass Clone(MyClass myClass)
    {
        MyClass clone;
        XmlSerializer ser = new XmlSerializer(typeof(MyClass), _xmlAttributeOverrides);
        using (var ms = new MemoryStream())
        {
            ser.Serialize(ms, myClass);
            ms.Position = 0;
            clone = (MyClass)ser.Deserialize(ms);
        }
        return clone;
    }
    

    请注意,此解决方案非常简单,但其性能不如其他解决方案。

    并且确保如果类增长,那么仍然只有克隆的字段,这些字段也将被序列化。