【问题标题】:Deep cloning objects深度克隆对象
【发布时间】:2010-09-09 20:42:21
【问题描述】:

我想做这样的事情:

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

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

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

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

【问题讨论】:

  • 可能有用:“为什么复制对象是一件可怕的事情?” agiledeveloper.com/articles/cloning072002.htm
  • stackoverflow.com/questions/8025890/… 另一个解决方案...
  • 你应该看看 AutoMapper
  • 您的解决方案要复杂得多,我读起来迷路了……呵呵。我正在使用 DeepClone 界面。公共接口 IDeepCloneable { T DeepClone(); }
  • @Pedro77 -- 不过,有趣的是,这篇文章最后说要在类上创建一个clone 方法,然后让它调用一个内部的私有构造函数,该构造函数通过this 传递。所以复制是可怕的[原文如此],但仔细复制(这篇文章绝对值得一读)不是。 ;^)

标签: c# .net clone


【解决方案1】:

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

对于深拷贝,它不可能知道如何自动完成。

【讨论】:

  • ICloneable 没有通用接口,因此不建议使用该接口。
【解决方案2】:
  1. 基本上需要实现ICloneable接口,然后实现对象结构拷贝。
  2. 如果它是所有成员的深层副本,您需要确保(与您选择的解决方案无关)所有孩子也是可克隆的。
  3. 有时您需要注意此过程中的一些限制,例如,如果您复制 ORM 对象,大多数框架只允许将一个对象附加到会话,并且您不得克隆该对象,或者如果您可能需要关心这些对象的会话附加。

干杯。

【讨论】:

  • ICloneable 没有通用接口,因此不建议使用该接口。
  • 简单明了的答案是最好的。
【解决方案3】:

我更喜欢复制构造函数而不是克隆。意图更明确。

【讨论】:

  • .Net 没有复制构造函数。
  • 当然可以:new MyObject(objToCloneFrom) 只需声明一个将要克隆的对象作为参数的 ctor。
  • 这不是一回事。您必须手动将其添加到每个课程中,甚至不知道您是否要保证深层副本。
  • +1 表示复制 ctor。您还必须为每种类型的对象手动编写一个 clone() 函数,当您的类层次结构深入几级时,祝您好运。
  • 使用复制构造函数,你会失去层次结构。 agiledeveloper.com/articles/cloning072002.htm
【解决方案4】:

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

有关使用 ICloneable 进行克隆的更详细说明,请查看this article

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

查看Developer's Corner article 了解更多选项(感谢 Ian)。

【讨论】:

  • ICloneable 没有通用接口,因此不建议使用该接口。
  • 您的解决方案一直有效,直到它需要处理循环引用,然后事情开始变得复杂,最好尝试使用深度序列化实现深度克隆。
  • 不幸的是,并非所有对象都可以序列化,因此您也不能总是使用该方法。 Ian 的链接是迄今为止最全面的答案。
【解决方案5】:

虽然一种方法是实现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 via serialization.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>A deep copy of the object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", nameof(source));
        }

        // Don't serialize a null object, simply return the default for that object
        if (ReferenceEquals(source, null)) return default;

        using var Stream stream = new MemoryStream();
        IFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, source);
        stream.Seek(0, SeekOrigin.Begin);
        return (T)formatter.Deserialize(stream);
    }
}

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

如果您更喜欢使用 C# 3.0 的新 extension methods,请将方法更改为具有以下签名:

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

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

编辑(2015 年 1 月 10 日)我想我会重新审视这个,提到我最近开始使用 (Newtonsoft) Json 来执行此操作,它should be 更轻,并避免了 [Serializable ] 标签。 (NB @atconway 在 cmets 中指出私有成员不使用 JSON 方法克隆)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialization 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 (ReferenceEquals(source, null)) return default;

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

【讨论】:

  • stackoverflow.com/questions/78536/cloning-objects-in-c/… 有一个指向上述代码的链接 [并引用了另外两个这样的实现,其中一个更适合我的上下文]
  • 序列化/反序列化涉及大量不必要的开销。请参阅 C# 中的 ICloneable 接口和 .MemberWise() 克隆方法。
  • @David,同意,但是如果对象很轻,并且使用它时的性能对您的要求来说不是太高,那么这是一个有用的提示。我承认,我没有在循环中大量使用它来处理大量数据,但我从未见过任何性能问题。
  • @Amir: 实际上,否:typeof(T).IsSerializable 如果类型已被标记为 [Serializable] 属性,则也是如此。它不必实现ISerializable 接口。
  • 我只是想提一下,虽然这种方法很有用,而且我自己也用过很多次,但它与 Medium Trust 完全不兼容——所以如果你正在编写代码,请注意这需要兼容性。 BinaryFormatter 访问私有字段,因此不能在部分信任环境的默认权限集中工作。您可以尝试另一个序列化程序,但请确保您的调用者知道如果传入对象依赖于私有字段,则克隆可能并不完美。
【解决方案6】:

不使用ICloneable 的原因是不是,因为它没有通用接口。 The reason not to use it is because it's vague。不清楚你得到的是浅拷贝还是深拷贝。这取决于实施者。

是的,MemberwiseClone 进行浅拷贝,但 MemberwiseClone 的反面不是 Clone;也许是DeepClone,它不存在。当您通过其 ICloneable 接口使用对象时,您无法知道底层对象执行哪种克隆。 (并且 XML cmets 不会说清楚,因为您将获得接口 cmets 而不是对象的 Clone 方法上的那些。)

我通常做的只是简单地创建一个 Copy 方法,它完全符合我的要求。

【讨论】:

  • 我不清楚为什么 ICloneable 被认为是模糊的。给定一个像 Dictionary(Of T,U) 这样的类型,我希望 ICloneable.Clone 应该执行任何级别的深浅复制,以使新字典成为包含相同 T 和 U 的独立字典(结构内容,和/或对象引用)作为原始文件。哪里来的暧昧?可以肯定的是,继承 ISelf(Of T)(包括“Self”方法)的通用 ICloneable(Of T) 会好得多,但我不认为深克隆与浅克隆有歧义。
  • 你的例子说明了这个问题。假设您有一个 Dictionary。克隆的 Dictionary 是否应该与原始 Dictionary 具有 same Customer 对象,或者那些 Customer 对象的 copy?任何一个都有合理的用例。但 ICloneable 并不清楚你会得到哪一个。这就是它没有用的原因。
  • @Kyralessa Microsoft MSDN 文章实际上说明了这个问题,即不知道您是在请求深拷贝还是浅拷贝。
  • 来自重复 stackoverflow.com/questions/129389/… 的答案描述了基于递归 MembershipClone 的复制扩展
【解决方案7】:

我想出这个来克服 .NET 必须手动深拷贝 List 的缺点。

我用这个:

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,但这是不可能的,因为 yield 在匿名方法块中不起作用。

更好的是,使用通用 List 克隆器:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}

【讨论】:

    【解决方案8】:

    好吧,我在 Silverlight 中使用 ICloneable 时遇到了问题,但我喜欢序列化的想法,我可以序列化 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);
        }
    }
    

    【讨论】:

      【解决方案9】:

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

      【讨论】:

        【解决方案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 ) } );
                }
            };
        }
        

        【讨论】:

        • 不幸的是,这是有缺陷的。它相当于调用 objectOne.MyProperty = objectTwo.MyProperty (即,它只会复制引用)。它不会克隆属性的值。
        • 致亚历克斯·诺克利夫:问题的作者被问及“复制每个属性”而不是克隆。在大多数情况下,不需要精确复制属性。
        • 我考虑使用这种方法,但使用递归。因此,如果属性的值是引用,请创建一个新对象并再次调用 CopyTo。我只看到一个问题,所有使用的类都必须有一个没有参数的构造函数。有人试过这个吗?我还想知道这是否真的适用于包含 .net 类(如 DataRow 和 DataTable)的属性?
        • 作者要求进行深度克隆,以便他们可以“对未反映在原始对象中的新对象进行更改”。这个答案创建了一个浅克隆,其中对克隆中对象的任何更改都会更改原始对象。
        【解决方案11】:

        这是一个深拷贝实现:

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

        【讨论】:

        • 这看起来像按成员克隆,因为不知道引用类型属性
        • 如果您想要令人眼花缭乱的快速性能,请不要使用此实现:它使用反射,因此不会那么快。相反,“过早的优化是万恶之源”,所以在运行分析器之前忽略性能方面。
        • CreateInstanceOfType 没有定义?
        • 整数失败:“非静态方法需要一个目标。”
        【解决方案12】:

        按照以下步骤操作:

        • 定义一个ISelf&lt;T&gt;,其中包含一个返回T 的只读Self 属性和一个派生自ISelf&lt;T&gt; 并包含一个方法T Clone()ICloneable&lt;out T&gt;
        • 然后定义一个CloneBase 类型,它实现了一个protected virtual generic VirtualCloneMemberwiseClone 转换为传入的类型。
        • 每个派生类型都应该通过调用基本克隆方法来实现VirtualClone,然后执行任何需要执行的操作以正确克隆派生类型的父 VirtualClone 方法尚未处理的那些方面。

        为了最大限度地继承多功能性,公开公共克隆功能的类应该是sealed,但派生自一个基类,除了缺少克隆之外,该基类在其他方面是相同的。与其传递显式可克隆类型的变量,不如采用ICloneable&lt;theNonCloneableType&gt; 类型的参数。这将允许期望Foo 的可克隆衍生物与DerivedFoo 的可克隆衍生物一起使用的例程,但也允许创建Foo 的不可克隆衍生物。

        【讨论】:

          【解决方案13】:

          在大量阅读了此处链接的许多选项以及此问题的可能解决方案之后,我相信all the options are summarized pretty well at Ian P's link(所有其他选项都是这些选项的变体),最佳解决方案由Pedro77's link 在问题 cmets 上提供.

          因此,我将在此处复制这 2 个参考的相关部分。这样我们就可以:

          在升 C 中克隆对象的最佳做法!

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

          article Fast Deep Copy by Expression Trees 还具有通过序列化、反射和表达式树进行克隆的性能比较。

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

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

          他的所有文章都围绕着一个尝试适用于大多数情况的示例,使用 3 个对象:PersonBrainCity。我们想克隆一个人,它有自己的大脑,但在同一个城市。您可以想象上述任何其他方法可能带来的所有问题,也可以阅读本文。

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

          通过指定New 后跟类名来复制对象通常会导致代码不可扩展。使用克隆,原型模式的应用,是实现这一点的更好方法。但是,使用 C#(和 Java)中提供的 clone 也可能存在很大问题。最好提供一个受保护的(非公共)复制构造函数并从 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);
              }
              …
          }
          

          现在考虑从 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
          

          请注意,如果我们保留对象数量的计数,则此处实现的克隆将保留对象数量的正确计数。

          【讨论】:

          • MS 建议不要将ICloneable 用于公共成员。 “由于 Clone 的调用者不能依赖于执行可预测克隆操作的方法,我们建议不要在公共 API 中实现 ICloneable。” msdn.microsoft.com/en-us/library/… 但是,根据 Venkat Subramaniam 在您的链接文章中给出的解释,我认为在这种情况下使用是有意义的只要 ICloneable 对象的创建者对哪些属性应该深入了解vs. 浅拷贝(即深拷贝Brain,浅拷贝City)
          • 首先,我远不是这个主题(公共 API)的专家。我认为一次,MS 的评论很有意义。而且我认为假设该 API 的用户会有如此深刻的理解是不安全的。因此,只有在 公共 API 上实现它才有意义,前提是它对使用它的人来说真的无关紧要。我有某种 UML 非常明确地对每个属性进行区分可能会有所帮助。但我想听听有更多经验的人的意见。 :P
          • 您可以使用CGbR Clone Generator 并获得类似的结果,而无需手动编写代码。
          • 中间语言实现很有用
          • C#中没有final
          【解决方案14】:

          如果您已经在使用像 ValueInjecterAutomapper 这样的第三方应用程序,您可以执行以下操作:

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

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

          the ValueInjecter deep cloning sample on GitHub

          【讨论】:

            【解决方案15】:

            我想要一个克隆器,用于非常简单的对象,主要是基元和列表。如果您的对象是开箱即用的 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);
                }
            }
            

            【讨论】:

            • 该解决方案甚至比 BinaryFormatter 解决方案更快,.NET Serialization Performance Comparison
            • 谢谢。使用 C# 的 MongoDB 驱动程序附带的 BSON 序列化程序,我基本上可以做同样的事情。
            • 这对我来说是最好的方式,但是,我使用Newtonsoft.Json.JsonConvert,但它是一样的
            • 为此,要克隆的对象需要是可序列化的,如前所述 - 这也意味着它可能没有循环依赖
            • 我认为这是最好的解决方案,因为该实现可以应用于大多数编程语言。
            【解决方案16】:

            这会将一个对象的所有可读和可写属性复制到另一个对象。

             public class PropertyCopy<TSource, TTarget> 
                                    where TSource: class, new()
                                    where TTarget: class, new()
                    {
                        public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
                        {
                            if (src==null) return trg;
                            if (trg == null) trg = new TTarget();
                            var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                            if (properties != null && properties.Count() > 0)
                                fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                            if (fulllist == null || fulllist.Count() == 0) return trg;
            
                            fulllist.ForEach(c =>
                                {
                                    c.SetValue(trg, c.GetValue(src));
                                });
            
                            return trg;
                        }
                    }
            

            这就是你使用它的方式:

             var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                                        "Creation",
                                                                        "Description",
                                                                        "IdTicketStatus",
                                                                        "IdUserCreated",
                                                                        "IdUserInCharge",
                                                                        "IdUserRequested",
                                                                        "IsUniqueTicketGenerated",
                                                                        "LastEdit",
                                                                        "Subject",
                                                                        "UniqeTicketRequestId",
                                                                        "Visibility");
            

            或复制所有内容:

            var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);
            

            【讨论】:

              【解决方案17】:

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

              如何使用?

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

              var newInstance = source.GetClone();
              

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

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

              可以克隆什么?

              • 原始(int、uint、byte、double、char 等),已知不可变 类型(DateTime、TimeSpan、String)和委托(包括 动作、功能等)
              • 可以为空
              • T[] 数组
              • 自定义类和结构,包括泛型类和结构。

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

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

              有多快?

              解决方案比反射更快,因为在GetClone&lt;T&gt; 首次用于给定类型T 之前,成员信息只需收集一次。

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

              还有更多...

              documentation 上阅读有关生成表达式的更多信息。

              List&lt;int&gt; 的示例表达式调试列表:

              .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&lt;int&gt; 编写自己的Clone 方法吗?

              【讨论】:

              • 这个在 NuGet 上的机会有多大?这似乎是最好的解决方案。与NClone相比如何?
              • 我认为这个答案应该被更多次投票。手动实现 ICloneable 繁琐且容易出错,如果性能很重要并且需要在短时间内复制数千个对象,则使用反射或序列化会很慢。
              • 一点也不,你对反射有误,你应该正确地缓存它。在stackoverflow.com/a/34368738/4711853下方查看我的答案
              【解决方案18】:

              我创建了一个可接受的答案版本,该版本适用于“[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;
                  }
              
              } 
              

              【讨论】:

                【解决方案19】:

                在方法内部重铸怎么样 这应该基本上调用一个自动复制构造函数

                T t = new T();
                T t2 = (T)t;  //eh something like that
                
                        List<myclass> cloneum;
                        public void SomeFuncB(ref List<myclass> _mylist)
                        {
                            cloneum = new List<myclass>();
                            cloneum = (List < myclass >) _mylist;
                            cloneum.Add(new myclass(3));
                            _mylist = new List<myclass>();
                        }
                

                似乎对我有用

                【讨论】:

                • 尝试使用具有简单类型和引用类型的属性的对象进行重铸。只对作为引用类型的属性进行了浅拷贝。
                【解决方案20】:

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

                只需将此功能添加到您的课程中:

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

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

                yourClass newLine = oldLine.DeepCopy();
                

                希望这会有所帮助。

                【讨论】:

                【解决方案21】:

                编辑:项目已终止

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

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

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

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

                【讨论】:

                • 这个好像挺好用的
                • 从一个代码快照开始工作比从整个系统开始工作更容易,尤其是关闭一个。没有任何图书馆可以一键解决所有问题,这是完全可以理解的。应该放松一下。
                • 我已经尝试过您的解决方案,它似乎运作良好,谢谢!我认为这个答案应该被更多次投票。手动实现 ICloneable 繁琐且容易出错,如果性能很重要并且需要在短时间内复制数千个对象,则使用反射或序列化会很慢。
                • 我试过了,但对我来说根本没用。引发 MemberAccess 异常。
                • 它不适用于较新版本的 .NET 并且已停产
                【解决方案22】:

                我喜欢这样的 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));
                        }
                    }
                

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

                【讨论】:

                  【解决方案23】:

                  如果你的对象树是可序列化的,你也可以使用类似的东西

                  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 增长,仍然只会克隆那些字段,这些字段也会被序列化。

                  【讨论】:

                    【解决方案24】:

                    您可以在 IClonable 接口上花费多少精力,这令人难以置信——尤其是在您拥有繁重的类层次结构的情况下。此外,MemberwiseClone 的工作方式也有些奇怪——它甚至不能完全克隆普通的 List 类型的结构。

                    当然,序列化最有趣的困境是序列化反向引用 - 例如具有父子关系的类层次结构。 我怀疑二进制序列化程序在这种情况下能否为您提供帮助。 (最终会出现递归循环+堆栈溢出)。

                    我有点喜欢这里提出的解决方案:How do you do a deep copy of an object in .NET (C# specifically)?

                    但是 - 它不支持列表,添加了支持,还考虑了重新育儿。 对于我已将该字段或属性设置为“父”的唯一规则,DeepClone 将忽略它。您可能想决定自己的反向引用规则 - 对于树层次结构,它可能是“左/右”等...

                    这里是整个代码 sn-p 包括测试代码:

                    using System;
                    using System.Collections;
                    using System.Collections.Generic;
                    using System.Diagnostics;
                    using System.Linq;
                    using System.Reflection;
                    using System.Text;
                    
                    namespace TestDeepClone
                    {
                        class Program
                        {
                            static void Main(string[] args)
                            {
                                A a = new A();
                                a.name = "main_A";
                                a.b_list.Add(new B(a) { name = "b1" });
                                a.b_list.Add(new B(a) { name = "b2" });
                    
                                A a2 = (A)a.DeepClone();
                                a2.name = "second_A";
                    
                                // Perform re-parenting manually after deep copy.
                                foreach( var b in a2.b_list )
                                    b.parent = a2;
                    
                    
                                Debug.WriteLine("ok");
                    
                            }
                        }
                    
                        public class A
                        {
                            public String name = "one";
                            public List<String> list = new List<string>();
                            public List<String> null_list;
                            public List<B> b_list = new List<B>();
                            private int private_pleaseCopyMeAsWell = 5;
                    
                            public override string ToString()
                            {
                                return "A(" + name + ")";
                            }
                        }
                    
                        public class B
                        {
                            public B() { }
                            public B(A _parent) { parent = _parent; }
                            public A parent;
                            public String name = "two";
                        }
                    
                    
                        public static class ReflectionEx
                        {
                            public static Type GetUnderlyingType(this MemberInfo member)
                            {
                                Type type;
                                switch (member.MemberType)
                                {
                                    case MemberTypes.Field:
                                        type = ((FieldInfo)member).FieldType;
                                        break;
                                    case MemberTypes.Property:
                                        type = ((PropertyInfo)member).PropertyType;
                                        break;
                                    case MemberTypes.Event:
                                        type = ((EventInfo)member).EventHandlerType;
                                        break;
                                    default:
                                        throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
                                }
                                return Nullable.GetUnderlyingType(type) ?? type;
                            }
                    
                            /// <summary>
                            /// Gets fields and properties into one array.
                            /// Order of properties / fields will be preserved in order of appearance in class / struct. (MetadataToken is used for sorting such cases)
                            /// </summary>
                            /// <param name="type">Type from which to get</param>
                            /// <returns>array of fields and properties</returns>
                            public static MemberInfo[] GetFieldsAndProperties(this Type type)
                            {
                                List<MemberInfo> fps = new List<MemberInfo>();
                                fps.AddRange(type.GetFields());
                                fps.AddRange(type.GetProperties());
                                fps = fps.OrderBy(x => x.MetadataToken).ToList();
                                return fps.ToArray();
                            }
                    
                            public static object GetValue(this MemberInfo member, object target)
                            {
                                if (member is PropertyInfo)
                                {
                                    return (member as PropertyInfo).GetValue(target, null);
                                }
                                else if (member is FieldInfo)
                                {
                                    return (member as FieldInfo).GetValue(target);
                                }
                                else
                                {
                                    throw new Exception("member must be either PropertyInfo or FieldInfo");
                                }
                            }
                    
                            public static void SetValue(this MemberInfo member, object target, object value)
                            {
                                if (member is PropertyInfo)
                                {
                                    (member as PropertyInfo).SetValue(target, value, null);
                                }
                                else if (member is FieldInfo)
                                {
                                    (member as FieldInfo).SetValue(target, value);
                                }
                                else
                                {
                                    throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
                                }
                            }
                    
                            /// <summary>
                            /// Deep clones specific object.
                            /// Analogue can be found here: https://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
                            /// This is now improved version (list support added)
                            /// </summary>
                            /// <param name="obj">object to be cloned</param>
                            /// <returns>full copy of object.</returns>
                            public static object DeepClone(this object obj)
                            {
                                if (obj == null)
                                    return null;
                    
                                Type type = obj.GetType();
                    
                                if (obj is IList)
                                {
                                    IList list = ((IList)obj);
                                    IList newlist = (IList)Activator.CreateInstance(obj.GetType(), list.Count);
                    
                                    foreach (object elem in list)
                                        newlist.Add(DeepClone(elem));
                    
                                    return newlist;
                                } //if
                    
                                if (type.IsValueType || type == typeof(string))
                                {
                                    return obj;
                                }
                                else if (type.IsArray)
                                {
                                    Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty));
                                    var array = obj as Array;
                                    Array copied = Array.CreateInstance(elementType, array.Length);
                    
                                    for (int i = 0; i < array.Length; i++)
                                        copied.SetValue(DeepClone(array.GetValue(i)), i);
                    
                                    return Convert.ChangeType(copied, obj.GetType());
                                }
                                else if (type.IsClass)
                                {
                                    object toret = Activator.CreateInstance(obj.GetType());
                    
                                    MemberInfo[] fields = type.GetFieldsAndProperties();
                                    foreach (MemberInfo field in fields)
                                    {
                                        // Don't clone parent back-reference classes. (Using special kind of naming 'parent' 
                                        // to indicate child's parent class.
                                        if (field.Name == "parent")
                                        {
                                            continue;
                                        }
                    
                                        object fieldValue = field.GetValue(obj);
                    
                                        if (fieldValue == null)
                                            continue;
                    
                                        field.SetValue(toret, DeepClone(fieldValue));
                                    }
                    
                                    return toret;
                                }
                                else
                                {
                                    // Don't know that type, don't know how to clone it.
                                    if (Debugger.IsAttached)
                                        Debugger.Break();
                    
                                    return null;
                                }
                            } //DeepClone
                        }
                    
                    }
                    

                    【讨论】:

                      【解决方案25】:

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

                      • 如果您想要 .NET 能够提供的最快速度,请选择此答案。
                      • 如果您想要一种非常非常简单的克隆方法,请忽略此答案。

                      换句话说,go with another answer unless you have a performance bottleneck that needs fixing, and you can prove it with a profiler

                      比其他方法快 10 倍

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

                      • 比任何涉及序列化/反序列化的方法快 10 倍;
                      • 非常接近 .NET 的理论最大速度。

                      还有方法……

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

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

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

                      • 嵌套结构上的嵌套 MemberwiseClone 需要 1.08 秒
                      • 嵌套类中的 Nested MemberwiseClone 需要 4.77 秒
                      • 序列化/反序列化 39.93 秒

                      在类上使用 Nested 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();
                      }
                      

                      再次注意,如果您使用 Nested MemberwiseClone 进行深层复制,您必须为类中的每个嵌套级别手动实现一个 ShallowCopy,以及一个 DeepCopy调用所有所说的 ShallowCopy 方法来创建一个完整的克隆。这很简单:总共只有几行代码,见上面的演示代码。

                      值类型与引用类型

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

                      • 如果你有一个“struct”,它是一个值类型,所以你可以复制它,内容将被克隆(但它只会做一个浅除非您使用本文中的技术,否则克隆)。
                      • 如果你有一个“class”,它是一个引用类型,所以如果你复制它,你所做的就是复制指向它的指针。要创建真正的克隆,您必须更有创意,并使用differences between value types and references types 在内存中创建原始对象的另一个副本。

                      differences between value types and references types

                      校验和帮助调试

                      • 错误地克隆对象会导致非常难以确定的错误。在生产代码中,我倾向于实施校验和来仔细检查对象是否已正确克隆,并且没有被另一个引用损坏。可以在发布模式下关闭此校验和。
                      • 我发现这种方法非常有用:通常,您只想克隆对象的一部分,而不是整个对象。

                      对于将许多线程与许多其他线程解耦非常有用

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

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

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

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

                      更新

                      显然,ExpressMapper 与上述手动编码一样快,甚至更快。我可能需要看看他们如何与分析器进行比较。

                      【讨论】:

                      • 如果你复制一个结构你得到一个浅拷贝,你可能仍然需要一个深拷贝的特定实现。
                      • @Lasse V. Karlsen。是的,你是绝对正确的,我已经更新了答案以使其更清楚。此方法可用于制作结构 类的深层副本。您可以运行包含的示例演示代码来展示它是如何完成的,它有一个深度克隆嵌套结构的示例,以及另一个深度克隆嵌套类的示例。
                      【解决方案26】:

                      当使用 Marc Gravells protobuf-net 作为您的序列化程序时,接受的答案需要稍作修改,因为要复制的对象不会被归因于 [Serializable],因此不可序列化并且克隆方法会抛出一个例外。
                      我修改它以使用 protobuf-net:

                      public static T Clone<T>(this T source)
                      {
                          if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
                                 == null)
                          {
                              throw new ArgumentException("Type has no ProtoContract!", "source");
                          }
                      
                          if(Object.ReferenceEquals(source, null))
                          {
                              return default(T);
                          }
                      
                          IFormatter formatter = ProtoBuf.Serializer.CreateFormatter<T>();
                          using (Stream stream = new MemoryStream())
                          {
                              formatter.Serialize(stream, source);
                              stream.Seek(0, SeekOrigin.Begin);
                              return (T)formatter.Deserialize(stream);
                          }
                      }
                      

                      这会检查是否存在 [ProtoContract] 属性并使用 protobufs 自己的格式化程序来序列化对象。

                      【讨论】:

                        【解决方案27】:

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

                        如果你能正确缓存它,它会在 4.6 秒内深度克隆 1000000 个对象(由 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

                        【讨论】:

                        • 调用prop.GetValue(...) 仍然是反射,无法缓存。虽然在表达式树中编译,所以速度更快
                        【解决方案28】:

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

                        https://github.com/kalisohn/CloneBehave

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

                        例如:下面的代码将deepClone地址,但只执行_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
                        

                        【讨论】:

                          【解决方案29】:

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

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

                          【讨论】:

                            【解决方案30】:

                            保持简单并使用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();
                            

                            【讨论】:

                            • 小心这个,它的性能真的很差。我最终切换到 johnc 答案,它和这个一样短并且表现更好。
                            • 这只是浅拷贝。
                            猜你喜欢
                            • 2012-06-05
                            • 2013-09-06
                            • 2013-10-11
                            • 2018-03-21
                            • 2019-03-25
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多