提问



我想做的事情如下:


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


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


我不经常需要这个功能,所以当它有必要时,我已经使用了创建一个新对象然后单独复制每个属性,但它总是让我觉得有更好或更优雅的方式处理这种情况。


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

最佳参考


虽然标准的做法是实现ICloneable界面(这里描述,所以我不会反感),这里是一个很好的深度克隆对象复印机,我在前一段时间在代码项目中找到并将其合并到我们的东西中。 [83] [85]


正如其他地方所提到的,它确实需要您的对象可序列化。


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


这个想法是它序列化你的对象,然后将它反序列化为一个新的对象。好处是,当对象变得过于复杂时,您不必担心克隆所有内容。


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


如果您更喜欢使用C#3.0的新扩展方法,请将方法更改为具有以下签名:[86]


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


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


编辑(2015年1月10日)我想重新审视一下,提到我最近开始使用(Newtonsoft)Json这样做,它应该更轻,并且避免了[[Serializable]]标签的开销。( NB @atconway在评论中指出私有成员不是使用JSON方法克隆的。[87]


/// <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);
}

其它参考1


我想要一个非常简单的对象,主要是原始和列表的克隆人。如果你的对象是开箱即用的JSON serializable,那么这个方法就可以了。这不需要修改或实现克隆类上的接口,只需要像JSON.NET这样的JSON序列化程序。


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

其它参考2


不使用ICloneable的原因是,因为它没有通用接口。不使用它的原因是因为它含糊不清。它并不清楚你是否得到浅或副本;这取决于实施者。[88] [89]


是的,MemberwiseClone是一个浅层副本,但与MemberwiseClone相反的是[[t Clone;它可能是DeepClone,它不存在。当您通过其ICloneable接口使用对象时,您无法知道底层对象执行哪种克隆。(并且XML注释不会明确,因为您将获得接口注释而不是对象上的注释克隆方法。)


我通常做的只是制作一个完全符合我想要的Copy方法。

其它参考3


在对这里链接的许多选项以及此问题的可能解决方案进行了大量阅读之后,我相信所有选项都在 Ian P 的链接中得到了很好的总结(所有其他选项都是这些选项的变体)最佳解决方案是由 Pedro77 提供的关于问题评论的链接。[90] [91]


所以我将在这里复制这两个引用的相关部分。这样我们可以:


在c sharp中克隆对象最好的办法!



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



  • 手动使用 ICloneable , Shallow 而非 Type-Safe

  • MemberwiseClone ,它使用ICloneable

  • 使用Activator.CreateInstance和递归的MemberwiseClone 反思
  • 序列化,正如johnc的首选答案
  • 所指出的那样
  • 中级语言,我不知道如何运作

  • 扩展方法,例如Havard Straden的此自定义克隆框架

  • 表达树



文章Fast Deep Copy by Expression Trees还对序列化,反射和表达树的克隆进行了性能比较。[92] [93] [94] [95] [96] [97] [99] [100] [101] [102]


为什么我选择 ICloneable (即手动)



Venkat Subramaniam先生(这里的冗余链接)详细解释了原因。[103]


他的所有文章都围绕着一个试图适用于大多数情况的示例,使用了3个对象: Person , Brain 和 City 。我们想要克隆一个人,它将拥有自己的大脑但是同一个城市。你可以想象上面的任何其他方法可以带来或阅读文章的所有问题。


这是他对他的结论的略微修改版本:



  通过指定New后跟类名来复制对象通常会导致代码无法扩展。使用clone,原型模式的应用,是实现这一目标的更好方法。但是,使用C#(和Java)中提供的克隆也很成问题。最好提供受保护(非公共)的复制构造函数,并从克隆方法中调用它。这使我们能够将创建对象的任务委托给类本身的实例,从而提供可扩展性,并使用受保护的拷贝构造函数安全地创建对象。



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


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);
    }
    …
}


现在考虑从Person派生一个类。


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


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

其它参考4


我更喜欢复制构造函数到克隆。意图更清晰。

其它参考5


简单的扩展方法来复制所有公共属性。适用于任何对象,要求类为[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 ) } );
        }
    };
}

其它参考6


好吧,我在Silverlight中使用ICloneable时遇到了问题,但是我喜欢seralization的想法,我可以seralize XML,所以我这样做:


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

其它参考7


如果你已经使用了像ValueInjecter或Automapper这样的第三方应用程序,你可以这样做:[104] [105]


MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax


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


在CodePlex上看到值得深入克隆的解决方案。[106]

其它参考8


我刚刚创建了 CloneExtensions项目。它使用Expression Tree运行时代码编译生成的简单赋值操作执行快速,深度克隆。[107]


如何使用?


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


var newInstance = source.GetClone();


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


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


什么可以克隆?



  • Primitive(int,uint,byte,double,char等),已知不可变
    types(DateTime,TimeSpan,String)和委托(包括
    动作,功能等)

  • 可空

  • T [[]]数组

  • 自定义类和结构,包括泛型类和结构。



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



  • 公共值,而非只读字段

  • 包含get和set访问器的公共属性的值

  • 实施ICollection的类型的集合项



它有多快?


解决方案比反射更快,因为在给定类型T第一次使用GetClone<T>之前,成员信息只需要收集一次。


当你克隆更多然后耦合相同类型的实例时,它也比基于序列化的解决方案更快T


以及更多......


阅读有关文档中生成的表达式的更多信息。[108]


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;
}


是不是很像你为List<int>编写自己的Clone方法?

其它参考9


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


有关使用ICloneable进行克隆的更详细说明,请查看本文。[109]


long 答案是它取决于。正如其他人所提到的,ICloneable不受泛型支持,需要对循环类引用进行特殊考虑,并且实际上被某些人视为.NET Framework中的错误。序列化方法取决于您的对象是可序列化的,它们可能不是,您可能无法控制。社区中仍然存在很多争论,即最佳做法。实际上,对于像ICloneable最初被解释为的所有情况而言,没有一种解决方案是一刀切的最佳实践。[110]


有关更多选项,请参阅此开发人员的角落文章(归功于Ian)。[111]

其它参考10


如果你想要真正的克隆到未知类型,你可以看看
fastclone。[112]


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


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


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

其它参考11


最好是实现扩展方法之类的


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


然后在解决方案的任何地方使用它


var copy = anyObject.DeepClone();


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



  1. 按序列化(最短代码)

  2. 反思 - 快5倍

  3. 按表达式树 - 快20倍



所有相关方法都运作良好,经过深入测试。 [114] [115]

其它参考12



  1. 基本上你需要实现ICloneable接口然后实现对象结构复制。

  2. 如果它是所有成员的深层副本,您需要保证(不与您选择的解决方案相关)所有子都可以克隆。

  3. 有时您需要在此过程中注意一些限制,例如,如果您复制ORM对象,大多数框架只允许一个对象附加到会话,并且您不能创建此对象的克隆,或者如果它是可能你需要关心这些对象的会话附加。



干杯。

其它参考13


我想出了这个来克服.NET必须手动深度复制List< T>的缺点。[116]


我用这个:


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;
}


我试图想出这样做的oneliner,但是由于不能在匿名方法块内工作,所以不可能。


更好的是,使用通用List< 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();
        }
    }
}

其它参考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;
}

其它参考15


保持简单并像其他人一样使用AutoMapper,它是一个将一个对象映射到另一个对象的简单小库...要将对象复制到另一个具有相同类型的对象,您只需要三行代码:[117]


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();

其它参考16


通常,您实现ICloneable接口并自己实现Clone。
C#对象具有内置的MemberwiseClone方法,该方法执行浅拷贝,可以帮助您处理所有原语。


对于深层复制,它无法知道如何自动执行。

其它参考17


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

其它参考18


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




  • 如果您希望以最快的速度运行,请选择此答案。

  • 如果您想要一种非常简单的克隆方法,请忽略此答案。



换句话说,除非你有一个需要修复的性能瓶颈,否则请使用另一个答案,你可以用一个分析器来证明它。[118]


比其他方法快10倍



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



  • 比涉及序列化/反序列化的任何内容快10倍;

  • 非常接近.NET能够达到的理论最高速度。



方法......



要获得最高速度,您可以使用嵌套的MemberwiseClone进行深层复制。它与复制值结构的速度几乎相同,并且比(a)反射或(b)序列化快得多(如本页其他答案中所述)。


请注意如果使用嵌套的MemberwiseClone进行深层复制,则必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有所述的ShallowCopy方法创建一个完整的克隆。这很简单:总共只有几行,请参阅下面的演示代码。


以下是代码的输出,显示100,000个克隆的相对性能差异:



  • 嵌套结构上嵌套的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调用demo:


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,并调用所有所说的DeepCopy ShallowCopy方法创建完整的克隆。这很简单:总共只有几行,请参阅上面的演示代码。


值类型与引用类型



请注意,在克隆对象时, struct 之间存在很大差异:



  • 如果你有一个结构,那么它就是值类型,所以你可以复制它,然后克隆内容(但它只能制作一个除非你使用这篇文章中的技巧,否则浅克隆。)

  • 如果您有,则它是引用类型,因此如果您复制它,您所做的只是将指针复制到它。一个真正的克隆,你必须更有创意,并使用值类型和引用类型之间的差异,这将在内存中创建原始对象的另一个副本。



查看值类型和引用类型之间的差异。[119] [120]


校验和以帮助调试




  • 错误地克隆对象可能导致非常难以确定的错误。在生产代码中,我倾向于实现校验和以仔细检查对象是否已正确克隆,并且没有被另一个对它的引用所破坏。可以在发布模式下关闭此校验和。

  • 我发现这个方法非常有用:通常,你只想克隆对象的一部分,而不是整个东西。



非常适用于将许多线程与许多其他线程分离



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



  • 我们可以有一个(或多个)线程修改他们拥有的类,然后将此类的完整副本推送到ConcurrentQueue

  • 然后我们有一个(或多个)线程将这些类的副本拉出来处理它们。



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


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


更新



显然,ExpressMapper与上面的手动编码一样快,如果不是更快的话。我可能要看看它们与分析器的比较。

其它参考19


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


https://github.com/kalisohn/CloneBehave[122]


也可作为nuget包提供:
https://www.nuget.org/packages/Clone.Behave/1.0.0[123]


例如:以下代码将为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

其它参考20


这个方法为我解决了这个问题:


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);

其它参考21


我喜欢像这样的Copyconstructors:


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


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

其它参考22


代码生成器



我们已经看到了从手动实现的序列化到反射的很多想法,我想使用CGbR代码生成器提出一种完全不同的方法。生成克隆方法是内存和CPU效率,因此比标准DataContractSerializer快300倍。[124]


你需要的只是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;
    }
}


注意:最新版本有更多的空检查,但我将它们排除在外以便更好地理解。

其它参考23


这里的解决方案快速而简单,对我而言无需继续进行序列化/反序列化。


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();
}

其它参考24


按着这些次序:



  • 使用只读Self属性定义ISelf<T>,返回TICloneable<out T>,它们派生自ISelf<T>并包含方法T Clone()

  • 然后定义一个CloneBase类型,它实现protected virtual generic VirtualClone投射MemberwiseClone到传入类型。

  • 每个派生类型都应该通过调用基本克隆方法来实现VirtualClone,然后做任何需要做的事情来正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。



为了最大限度地继承多功能性,暴露公共克隆功能的类应该是sealed,但是派生自基类,除了缺少克隆之外,基类是相同的。不要传递显式clonable类型的变量,而是采用类型ICloneable<theNonCloneableType>的参数。这将允许一个期望Foo的可克隆衍生物的例程与DerivedFoo的可克隆衍生物一起使用,但也允许创建Foo的非可克隆衍生物。

其它参考25


我想你可以尝试一下。


MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it

其它参考26


我创建了一个可接受的答案版本,该答案适用于[[Serializable]]和[[DataContract]]。我写这篇文章已经有一段时间了,但是如果我记得那么[[DataContract]]需要一个不同的序列化器。


需要 System,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;
    }

} 

其它参考27


要克隆类对象,可以使用Object.MemberwiseClone方法,


只需将此功能添加到您的班级:


public class yourClass
{
    // ...
    // ...

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


然后执行深度独立副本,只需调用DeepCopy方法:


yourClass newLine = oldLine.DeepCopy();


希望这可以帮助。

其它参考28


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


如果你正确地缓存它,那么它将深度克隆1000000个对象4,6s(由Watcher测量)。


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

其它参考29


如果您的对象树是可序列化的,您也可以使用这样的东西


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;
}


被告知这个解决方案非常简单,但它不像其他解决方案那样高效。


并确保如果Class增长,仍然只会克隆那些字段,这些字段也会被序列化。