深度克隆对象

Deep cloning objects

提问人:NakedBrunch 提问时间:9/17/2008 最后编辑:Vivek NunaNakedBrunch 更新时间:6/26/2023 访问量:1005275

问:

我想做这样的事情:

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

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

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

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

C# .NET 克隆 深层复制

评论

109赞 Pedro77 12/7/2011
可能有用:“为什么复制对象是一件可怕的事情?agiledeveloper.com/articles/cloning072002.htm
2赞 Felix K. 3/17/2012
stackoverflow.com/questions/8025890/......另一种解决方案...
27赞 Daniel Little 12/19/2012
您应该看看 AutoMapper
4赞 Pedro77 8/9/2013
你的解决方案要复杂得多,我迷路了......呵呵呵。我正在使用 DeepClone 界面。公共接口 IDeepCloneable<T> { T DeepClone();
4赞 ruffin 9/6/2014
@Pedro77 -- 不过,有趣的是,那篇文章最终说要在类上创建一个方法,然后让它调用一个内部的、私有的构造函数,然后传递。所以抄袭是不容易的[原文如此],但仔细抄袭(这篇文章绝对值得一读)就不是了。;^)clonethis

答:

13赞 HappyDude 9/17/2008 #1

通常,您可以实现 ICloneable 接口并自行实现克隆。 C# 对象具有内置的 MemberwiseClone 方法,该方法执行浅拷贝,可以帮助您解决所有基元的问题。

对于深层拷贝,它无法知道如何自动执行此操作。

评论

1赞 Karg 9/17/2008
ICloneable 没有泛型接口,因此不建议使用该接口。
25赞 dimarzionist 9/17/2008 #2
  1. 基本上,你需要实现 ICloneable 接口,然后实现对象结构复制。
  2. 如果它是所有成员的深层副本,则需要确保(与您选择的解决方案无关)所有子项也是可克隆的。
  3. 有时你需要注意在这个过程中的一些限制,例如,如果你复制ORM对象,大多数框架只允许一个对象附加到会话,你不能克隆这个对象,或者如果可能的话,你需要关心这些对象的会话附加。

干杯。

评论

4赞 Karg 9/17/2008
ICloneable 没有泛型接口,因此不建议使用该接口。
0赞 DavidGuaita 4/20/2018
简单明了的答案是最好的。
96赞 Nick 9/17/2008 #3

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

评论

7赞 Pop Catalin 9/17/2008
.Net 没有复制构造函数。
57赞 Nick 9/17/2008
当然可以: new MyObject(objToCloneFrom) 只需声明一个 ctor,它将要克隆的对象作为参数。
37赞 Dave Van den Eynde 6/4/2009
这不是一回事。你必须手动将它添加到每个类中,你甚至不知道你是否在获得一个深度副本。
18赞 Andrew Grant 9/15/2009
+1 用于复制 CTOR。你也必须为每种类型的对象手动编写一个clone()函数,当你的类层次结构深入到几个层次时,祝你好运。
6赞 Will 11/7/2011
但是,使用复制构造函数时,您会丢失层次结构。agiledeveloper.com/articles/cloning072002.htm
26赞 Zach Burlingame 9/17/2008 #4

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

有关使用 ICloneable 进行克隆的更详细说明,请查看此文章

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

有关更多选项,请参阅此开发人员专区文章(感谢 Ian)。

评论

1赞 Karg 9/17/2008
ICloneable 没有泛型接口,因此不建议使用该接口。
0赞 Pop Catalin 9/17/2008
您的解决方案一直有效,直到它需要处理循环引用,然后事情开始变得复杂,最好尝试使用深度序列化实现深度克隆。
0赞 Zach Burlingame 9/17/2008
遗憾的是,并非所有对象都是可序列化的,因此也不能始终使用该方法。伊恩的链接是迄今为止最全面的答案。
1917赞 23 revs, 17 users 56%johnc #5

虽然一种方法是实现 ICloneable 接口(在这里描述,所以我不会反刍),但这里有一个不错的深度克隆对象复制器,我不久前在代码项目上找到了它,并将其合并到我们的代码中。 如前所述,它要求对象是可序列化的。

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 的新扩展方法,请将方法更改为具有以下签名:

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

现在,方法调用只是变成了 .objectBeingCloned.Clone();

编辑(2015 年 1 月 10 日)我以为我会重新审视这一点,提到我最近开始使用 (Newtonsoft) Json 来做这件事,它应该更轻,并避免 [Serializable] 标签的开销。(注意:@atconway在评论中指出,私有成员不是使用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);
}

评论

30赞 Ruben Bartelink 2/4/2009
stackoverflow.com/questions/78536/cloning-objects-in-c/......有一个指向上面代码的链接 [并引用了另外两个这样的实现,其中一个更适合我的上下文]
121赞 3Dave 1/29/2010
序列化/反序列化涉及大量不必要的开销。请参见ICloneable 接口和 。C# 中的 MemberWise() 克隆方法。
22赞 johnc 1/29/2010
@David,当然,但是如果物体很轻,并且使用时的性能不会太高,无法满足您的要求,那么这是一个有用的提示。我承认,我没有在循环中大量数据中密集使用它,但我从未见过任何性能问题。
20赞 Daniel Gehriger 6/3/2011
@Amir:实际上,如果类型已使用属性标记,则 no: 也是如此。它不必实现接口。typeof(T).IsSerializable[Serializable]ISerializable
13赞 Alex Norcliffe 10/17/2011
我只是想提一下,虽然这种方法很有用,而且我自己也使用过很多次,但它与 Medium Trust 完全不兼容 - 所以如果你正在编写需要兼容性的代码,请注意。BinaryFormatter 访问私有字段,因此无法在部分信任环境的默认权限集中工作。您可以尝试使用另一个序列化程序,但请确保您的调用方知道,如果传入对象依赖于私有字段,则克隆可能并不完美。
202赞 Ryan Lundy 9/17/2008 #6

不使用 ICloneable 的原因不是因为它没有泛型接口。不使用它的原因是因为它含糊不清。它不清楚你得到的是浅拷贝还是深拷贝;这取决于实施者。

是的,做一个浅层的副本,但反面不是;也许是不存在的。通过对象的 ICloneable 接口使用对象时,无法知道基础对象执行哪种克隆。(XML 注释不会说得很清楚,因为你会得到接口注释,而不是对象的 Clone 方法上的注释。MemberwiseCloneMemberwiseCloneCloneDeepClone

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

评论

0赞 supercat 1/13/2011
我不清楚为什么 ICloneable 被认为是模糊的。给定像 Dictionary(Of T,U) 这样的类型,我希望 ICloneable.Clone 应该执行任何级别的深度和浅层复制,以使新字典成为一个独立的字典,其中包含与原始字典相同的 T 和 U(结构内容和/或对象引用)。歧义在哪里?可以肯定的是,继承了 ISelf(Of T) 的通用 ICloneable(Of T)(包括“Self”方法)会好得多,但我没有看到深度克隆与浅层克隆的歧义。
45赞 Ryan Lundy 1/13/2011
您的示例说明了该问题。假设您有一个 Dictionary<string, Customer>。克隆的 Dictionary 是否应具有与原始 Customer 对象相同的 Customer 对象,还是这些 Customer 对象的副本?任何一种都有合理的用例。但是 ICloneable 并没有明确说明你会得到哪一个。这就是为什么它没有用。
0赞 crush 5/29/2014
@Kyralessa Microsoft MSDN 文章实际上说明了这个问题,即不知道您请求的是深拷贝还是浅拷贝。
0赞 Michael Freidgeim 1/23/2018
重复 stackoverflow.com/questions/129389/ 的答案描述了基于递归 MembershipClone 的复制扩展
12赞 Daniel Mošmondor 9/30/2009 #7

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

我用这个:

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

在另一个地方:

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

我试图想出一个可以做到这一点的 oneliner,但这是不可能的,因为 yield 在匿名方法块中不起作用。

更好的是,使用通用的 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();
        }
    }
}
36赞 Michael White 12/3/2009 #8

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

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //[email protected]
    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赞 xr280xr 10/19/2010 #9

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

48赞 Konstantin Salavatov 3/16/2011 #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 ) } );
        }
    };
}

评论

20赞 Alex Norcliffe 10/18/2011
不幸的是,这是有缺陷的。它等效于调用 objectOne.MyProperty = objectTwo.MyProperty(即,它只会复制引用)。它不会克隆属性的值。
1赞 Konstantin Salavatov 3/28/2012
致亚历克斯·诺克利夫(Alex Norcliffe):问题的作者被问及“复制每个属性”而不是克隆。在大多数情况下,不需要精确复制属性。
2赞 Koryu 7/25/2013
我考虑使用这种方法,但使用递归。因此,如果属性的值是引用,请创建一个新对象并再次调用 CopyTo。我只看到一个问题,所有使用的类都必须有一个没有参数的构造函数。有人已经尝试过了吗?我还想知道这是否真的适用于包含 DataRow 和 DataTable 等 .NET 类的属性?
0赞 Andrew 10/7/2021
作者要求进行深度克隆,以便他们可以“对新对象进行未反映在原始对象中的更改”。此答案将创建一个浅层克隆,其中对克隆中对象的任何更改都将更改原始克隆。
9赞 dougajmcdonald 9/6/2011 #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;
}

评论

2赞 sll 11/6/2011
这看起来像成员克隆,因为不知道引用类型属性
1赞 Contango 12/31/2011
如果你想要令人眼花缭乱的快速性能,不要选择这种实现:它使用反射,所以它不会那么快。相反,“过早的优化是万恶之作”,所以在运行分析器之前,不要忽略性能方面。
1赞 Furkan Gözükara 10/15/2015
未定义 CreateInstanceOfType?
0赞 Mr.B 5/17/2016
它在 interger 上失败:“非静态方法需要目标。
6赞 supercat 12/8/2011 #12

请按照下列步骤操作:

  • 定义一个只读属性,该属性返回 和 ,该属性派生自并包含方法。ISelf<T>SelfTICloneable<out T>ISelf<T>T Clone()
  • 然后定义一个类型,该类型实现对传入类型的强制转换。CloneBaseprotected virtual generic VirtualCloneMemberwiseClone
  • 每个派生类型都应通过调用基克隆方法,然后执行任何需要执行的操作来实现,以正确克隆父 VirtualClone 方法尚未处理的派生类型的那些方面。VirtualClone

为了获得最大的继承通用性,公开公共克隆功能的类应该是 ,但派生自基类,除了缺少克隆之外,其他方面都是相同的。不要传递显式可克隆类型的变量,而是采用 类型的参数。这将允许期望 的可克隆导数与 的可克隆导数一起使用的例程,但也允许创建 的不可克隆导数。sealedICloneable<theNonCloneableType>FooDerivedFooFoo

160赞 cregox 9/27/2012 #13

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

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

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

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

文章 Fast Deep Copy by Expression Trees 还对 Serialization、Reflection 和 Expression Trees 克隆进行了性能比较。

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

Venkat Subramaniam先生(此处为冗余链接)详细解释了原因

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

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

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

希望这个实现可以把事情弄清楚:

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

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

评论

9赞 BateTech 1/10/2015
MS 建议不要用于公共成员。“由于克隆的调用方不能依赖于执行可预测克隆操作的方法,因此我们建议不要在公共 API 中实现 ICloneable。”msdn.microsoft.com/en-us/library/......但是,根据 Venkat Subramaniam 在您的链接文章中给出的解释,我认为在这种情况下使用是有意义的,只要 ICloneable 对象的创建者对哪些属性应该是深层和浅层副本(即深层复制 Brain,浅层复制 City)有深刻的理解ICloneable
0赞 cregox 1/10/2015
首先,我远不是这个主题(公共 API)的专家。我认为这一次,MS 的评论很有道理。而且我不认为假设该 API 的用户会有如此深刻的理解是不安全的。因此,只有在公共 API 上实现它才有意义,如果它对任何使用它的人来说都无关紧要。我想让某种 UML 非常明确地对每个属性进行区分可能会有所帮助。但我想听听有更多经验的人的意见。:P
0赞 Toxantron 6/10/2016
您可以使用 CGbR 克隆生成器并获得类似的结果,而无需手动编写代码。
0赞 Michael Freidgeim 1/23/2018
中间语言实现很有用
2赞 Konrad 9/5/2018
C 语言中没有决赛#
37赞 Michael Cox 10/16/2012 #14

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

MyObject oldObj; // The existing object to clone

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

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

请参阅 GitHub 上的 ValueInjecter 深度克隆示例

428赞 craastad 4/3/2013 #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);
    }
}

评论

21赞 esskar 3/12/2014
solutiojn 甚至比 BinaryFormatter 解决方案更快, .NET 序列化性能比较
5赞 Mark Ewer 6/18/2014
谢谢你。我能够使用 C# 的 MongoDB 驱动程序附带的 BSON 序列化程序做基本相同的事情。
6赞 Pierre 2/4/2015
这对我来说是最好的方式,但是,我使用但它是一样的Newtonsoft.Json.JsonConvert
4赞 radomeit 2/22/2018
为此,要做到这一点,要克隆的对象需要是可序列化的,如前所述 - 这也意味着,例如,它可能没有循环依赖关系
4赞 mr5 1/2/2019
我认为这是最好的解决方案,因为该实现可以应用于大多数编程语言。
1赞 2 revsYlli Prifti #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);
38赞 MarcinJuraszek 12/25/2013 #17

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

如何使用它?

与其编写自己的方法,不如使用表达式树让程序自己编写,并在字段和属性之间分配。 标记为扩展方法的方法允许您在实例上简单地调用它:CloneCopyGetClone<T>()

var newInstance = source.GetClone();

您可以选择应该从中复制到使用枚举的内容:sourcenewInstanceCloningFlags

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

可以克隆什么?

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

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

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

它有多快?

该解决方案比反射更快,因为成员信息只需收集一次,然后才能首次用于给定类型。GetClone<T>T

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

以及更多...

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

示例表达式调试列表: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;
}

这难道不是很像你写自己的方法吗?CloneList<int>

评论

2赞 crush 5/29/2014
这在 NuGet 上出现的可能性有多大?这似乎是最好的解决方案。它与 NClone 相比如何?
0赞 nightcoder 7/28/2015
我认为这个答案应该被点赞更多次。手动实现 ICloneable 既繁琐又容易出错,如果性能很重要,并且需要在短时间内复制数千个对象,则使用反射或序列化会很慢。
0赞 Roma Borodov 12/19/2015
一点也不,你对反射是错误的,你应该简单地正确地缓存它。在下面查看我的答案 stackoverflow.com/a/34368738/4711853
4赞 Jeroen Ritmeijer 4/12/2014 #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;
    }

} 
1赞 will_m 4/13/2014 #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>();
        }

似乎对我有用

评论

0赞 Simon Elms 6/10/2016
尝试使用具有简单类型和引用类型属性的对象进行重铸。只做了作为引用类型的属性的浅拷贝。
3赞 Chtioui Malek 4/25/2014 #20

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

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

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

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

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

yourClass newLine = oldLine.DeepCopy();

希望这会有所帮助。

评论

5赞 odyth 12/21/2014
MemberwiseClone 方法创建的是浅拷贝,而不是深拷贝。 msdn.microsoft.com/en-us/library/...
0赞 ahmed hamdy 5/27/2020
@odyth重要的注释作为实际代码 做浅淤贝,这里是关于克隆的好文章和每种类型的示例 geeksforgeeks.org/shallow-copy-and-deep-copy-in-c-sharp
0赞 Christopher 9/24/2021
到目前为止,这适用于我的情况。谢谢。
20赞 Michael Sander 2/16/2015 #21

编辑:项目已停产

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

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

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

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

评论

0赞 LuckyLikey 4/20/2015
这个似乎很有用
0赞 TarmoPikaro 4/25/2015
从一个代码快照开始工作比从整个系统(尤其是封闭的系统)开始工作更容易。可以理解的是,没有一个图书馆可以一枪解决所有问题。应该做一些放松。
1赞 nightcoder 7/28/2015
我已经尝试了您的解决方案,它似乎效果很好,谢谢!我认为这个答案应该被点赞更多次。手动实现 ICloneable 既繁琐又容易出错,如果性能很重要,并且需要在短时间内复制数千个对象,则使用反射或序列化会很慢。
0赞 Michael Brown 11/16/2018
我试过了,它对我根本不起作用。引发 MemberAccess 异常。
0赞 Michael Sander 11/19/2018
它不适用于较新版本的 .NET,并且已停产
7赞 LuckyLikey 3/6/2015 #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));
        }
    }

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

2赞 LuckyLikey 4/20/2015 #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;
}

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

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

2赞 TarmoPikaro 4/25/2015 #24

令人难以置信的是,你可以花多少精力在IClonable接口上 - 特别是如果你有沉重的类层次结构。此外,MemberwiseClone 的工作方式也很奇怪 - 它甚至不能完全克隆普通的 List 类型的结构。

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

我不知何故喜欢这里提出的解决方案:如何在 .NET(特别是 C#)中对对象进行深度复制?

但是 - 它不支持列表,并补充说,这种支持还考虑了重新父级。 对于我制定的仅父级规则,该字段或属性应命名为“parent”,然后 DeepClone 将忽略它。您可能希望决定自己的反向引用规则 - 对于树层次结构,它可能是“左/右”,等等......

以下是包括测试代码在内的完整代码片段:

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
    }

}
12赞 Contango 7/5/2015 #25

Q.我为什么会选择这个答案?

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

换句话说,除非您有需要修复的性能瓶颈,否则请选择另一个答案,并且您可以使用分析器来证明这一点

比其他方法快 10 倍

执行深度克隆的方法是:

  • 比任何涉及序列化/反序列化的东西都快 10 倍;
  • 非常接近 .NET 能够达到的理论最大速度。

而方法......

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

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

以下是代码的输出,显示了 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 调用演示:

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

值类型与引用类型

请注意,在克隆对象时,“结构”和“”之间有很大的区别:

  • 如果你有一个“结构”,它是一个值类型,所以你可以复制它,内容将被克隆(但除非你使用本文中的技术,否则它只会做一个浅层克隆)。
  • 如果你有一个“”,它就是一个引用类型,所以如果你复制它,你所做的就是复制指向它的指针。要创建真正的克隆,您必须更具创造性,并使用值类型和引用类型之间的差异,从而在内存中创建原始对象的另一个副本。

请参阅值类型和引用类型之间的差异

用于帮助调试的校验和

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

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

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

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

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

这种方法的速度也快得令人眼花缭乱:如果我们使用嵌套结构,它比序列化/反序列化嵌套类快 35 倍,并且允许我们利用机器上所有可用的线程。

更新

显然,ExpressMapper 与上述手动编码一样快,甚至更快。我可能不得不看看它们与分析器的比较。

评论

0赞 Lasse V. Karlsen 7/5/2015
如果复制结构,则会得到浅拷贝,可能仍需要深拷贝的特定实现。
0赞 Contango 7/5/2015
@Lasse V. Karlsen。是的,你是绝对正确的,我已经更新了答案以使其更清楚。此方法可用于创建结构类的深度副本。您可以运行包含的示例演示代码来演示它是如何完成的,它有一个深度克隆嵌套结构的示例,以及另一个深度克隆嵌套类的示例。
1赞 KeyNone 8/22/2015 #26

当使用 Marc Gravells protobuf-net 作为序列化程序时,接受的答案需要一些轻微的修改,因为要复制的对象不会被归因,因此不可序列化,并且 Clone 方法将引发异常。
我修改了它以使用 protobuf-net:
[Serializable]

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

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

4赞 Roma Borodov 12/19/2015 #27

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

如果您正确缓存它,那么它将在 1000000 秒内深度克隆 4,6 个对象(由 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

评论

2赞 Tseng 9/28/2016
调用仍然是反射,无法缓存。在表达式树中,它的编译速度更快prop.GetValue(...)
8赞 kalisohn 1/26/2016 #28

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

https://github.com/kalisohn/CloneBehave

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

例如:以下代码将深度克隆地址,但仅执行_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
7赞 GorvGoyl 4/12/2016 #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);

16赞 Stacked 5/28/2016 #30

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

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

目标对象现在是源对象的副本。 还不够简单?创建要在解决方案中的任何位置使用的扩展方法:Create an extension method to use anywhere in your solution:

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

评论

0赞 Agorilla 4/11/2017
小心这个,它的表现真的很差。我最终切换到 johnc 答案,它和这个一样短,而且表现要好得多。
5赞 N73k 7/2/2019
这只能进行浅层复制。
8赞 Toxantron 6/10/2016 #31

代码生成器

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

您所需要的只是一个部分类定义,生成器会完成剩下的工作:ICloneable

public partial class Root : ICloneable
{
    public Root(int number)
    {
        _number = number;
    }
    private int _number;

    public Partial[] Partials { get; set; }

    public IList<ulong> Numbers { get; set; }

    public object Clone()
    {
        return Clone(true);
    }

    private Root()
    {
    }
} 

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

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

7赞 Daniele D. 7/29/2016 #32

这是一个快速简便的解决方案,对我有用,无需中继序列化/反序列化。

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();
}
36赞 frakon 8/4/2016 #33

最好的方法是实现一个扩展方法,如

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

然后通过以下方式在解决方案中的任何位置使用它

var copy = anyObject.DeepClone();

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

  1. 按序列化(最短代码)
  2. 通过反射 - 速度提高 5 倍
  3. 通过表达式树 - 速度提高 20 倍

所有链接的方法都运行良好,并经过了深入测试。

评论

1赞 Mrinal Kamboj 12/24/2017
使用您 codeproject.com/Articles/1111658/ 发布的表达式树克隆代码...,在较新版本的 .Net 框架中失败,并出现安全异常,操作可能会破坏运行时的稳定性,这基本上是由于格式错误的表达式树导致的异常,该表达式树用于在运行时生成 Func,请检查您是否有一些解决方案。事实上,我只看到过具有深层次结构的复杂对象的问题,简单的对象很容易被复制
2赞 N73k 7/2/2019
ExpressionTree 的实现似乎非常好。它甚至可以与循环引用和私有成员一起使用。无需任何属性。我找到的最佳答案。
0赞 Adel Mourad 1/13/2020
最好的答案,效果很好,你救了我的一天
0赞 aca 2/2/2023
@MrinalKamboj 这个错误有什么解决方案吗?由于我也得到了它,在尝试克隆复杂对象时,到目前为止找不到解决方案。
1赞 Mrinal Kamboj 3/6/2023
@aca 不,我当时找不到解决方案
3赞 Sudhanva Kotabagi 8/20/2016 #34

我想你可以试试这个。

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

评论

0赞 Andrew 10/7/2021
两个问题 - 首先,c# 中没有自动构造函数会采用相同类型的对象,因此可能会出错。其次,如果创建了这样的构造函数,则它要么是浅层克隆,要么必须在对象属性的每一步都使用类似的构造函数来克隆包含的对象和集合。new MyObject(myObj);
-2赞 gaa 2/2/2017 #35

我知道这个问题和答案在这里坐了一段时间,接下来的不是完全的答案,而是观察,我最近在检查是否确实没有克隆私人时遇到了这个问题(如果我没有;)当我愉快地复制粘贴@johnc更新的答案时。

我只是简单地让自己扩展方法(这几乎是上述答案的复制粘贴形式):

public static class CloneThroughJsonExtension
{
    private static readonly JsonSerializerSettings DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

    public static T CloneThroughJson<T>(this T source)
    {
        return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), DeserializeSettings);
    }
}

并像这样天真地放弃了类(实际上还有更多,但它们是无关的):

public class WhatTheHeck
{
    public string PrivateSet { get; private set; } // matches ctor param name

    public string GetOnly { get; } // matches ctor param name

    private readonly string _indirectField;
    public string Indirect => $"Inception of: {_indirectField} "; // matches ctor param name
    public string RealIndirectFieldVaule => _indirectField;

    public WhatTheHeck(string privateSet, string getOnly, string indirect)
    {
        PrivateSet = privateSet;
        GetOnly = getOnly;
        _indirectField = indirect;
    }
}

和这样的代码:

var clone = new WhatTheHeck("Private-Set-Prop cloned!", "Get-Only-Prop cloned!", "Indirect-Field clonned!").CloneThroughJson();
Console.WriteLine($"1. {clone.PrivateSet}");
Console.WriteLine($"2. {clone.GetOnly}");
Console.WriteLine($"3.1. {clone.Indirect}");
Console.WriteLine($"3.2. {clone.RealIndirectFieldVaule}");

结果:

1. Private-Set-Prop cloned!
2. Get-Only-Prop cloned!
3.1. Inception of: Inception of: Indirect-Field cloned!
3.2. Inception of: Indirect-Field cloned!

我当时就想:什么...于是我抓起了Newtonsoft.Json Github repo,开始挖掘。 结果是:在反序列化恰好只有一个 ctor 的类型并且其参数名称与(不区分大小写)公共属性名称匹配时,它们将作为这些参数传递给 ctor。可以在代码中找到一些线索 这里 和 这里.

底线

我知道这种情况并不常见,示例代码有点滥用,但是嘿!当我检查灌木丛中是否有龙等着跳出来咬我的屁股时,这让我大吃一惊。;)

3赞 Mauro Sampietro 4/23/2017 #36

映射器执行深层复制。对于对象的每个成员,它会创建一个新对象并分配其所有值。它以递归方式处理每个非原始内部成员。

我建议你使用目前最快、最积极开发的之一。 我建议 UltraMapper https://github.com/maurosampietro/UltraMapper

Nuget 包:https://www.nuget.org/packages/UltraMapper/

评论

0赞 M.A.R. 4/23/2017
欢迎提供解决方案的链接,但请确保您的答案在没有它的情况下是有用的:在链接周围添加上下文,以便您的其他用户知道它是什么以及为什么它在那里,然后引用您链接到的页面中最相关的部分,以防目标页面不可用。只不过是链接的答案可能会被删除。
1赞 lindexi 8/8/2017 #37

我找到了一种新的方法,那就是 Emit。

我们可以使用 Emit 将 IL 添加到应用并运行它。但我不认为这是我想完善这一点的好方法,我写下了我的答案。

Emit 可以看到官方文档指南

您应该学习一些 IL 来阅读代码。我将编写可以在类中复制属性的代码。

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

代码可以是深层复制,但可以复制属性。如果你想让它成为深度复制,你可以为 IL 更改它太难了,我做不到。

2赞 Matthew Watson 3/14/2018 #38

另一个 JSON.NET 答案。此版本适用于未实现 ISerializable 的类。

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
    }

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}
1赞 Sameera R. 5/3/2018 #39

C# 扩展,也支持“not ISerializable”类型。

 public static class AppExtensions
 {                                                                      
       public static T DeepClone<T>(this T a)
       {
           using (var stream = new MemoryStream())
           {
               var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));

               serializer.Serialize(stream, a);
               stream.Position = 0;
               return (T)serializer.Deserialize(stream);
           }
       }                                                                    
 }

用法

       var obj2 = obj1.DeepClone()
2赞 qubits 10/12/2018 #40

通用方法在技术上都是有效的,但我只想补充一点,因为我们实际上很少需要真正的深度复制,我强烈反对在实际的业务应用程序中使用通用的深度复制,因为这样你可能会有很多地方复制对象,然后显式修改, 很容易迷路。

在大多数现实情况下,您还希望对复制过程进行尽可能精细的控制,因为您不仅与数据访问框架耦合,而且在实践中,复制的业务对象很少应该 100% 相同。想想 ORM 用来识别对象引用的示例 referenceId,完整的深层复制也会复制这个 id,所以在内存中对象会有所不同,一旦你把它提交到数据存储,它就会抱怨,所以你将不得不在复制后手动修改这个属性,如果对象发生变化,你需要在所有使用通用深度复制的地方调整它。

用 ICloneable 扩展@cregox答案,实际上什么是深拷贝?它只是堆上新分配的对象,与原始对象相同,但占用不同的内存空间,因此,与其使用通用克隆器功能,为什么不直接创建一个新对象呢?

我个人在我的领域对象上使用静态工厂方法的思想。

例:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

如果有人正在寻找如何构建对象实例化,同时保留对复制过程的完全控制,这是我个人非常成功的解决方案。受保护的构造函数也使它成为现实,其他开发人员被迫使用工厂方法,该方法提供了一个简洁的单点对象实例化,将构造逻辑封装在对象内部。如有必要,您还可以重载该方法,并在不同位置使用多个克隆逻辑。

5赞 Michael Brown 11/16/2018 #41

由于这个问题的几乎所有答案都不令人满意,或者在我的情况下显然不起作用,因此我编写了 AnyClone,它完全通过反思实现并解决了这里的所有需求。我无法让序列化在具有复杂结构的复杂场景中工作,并且不太理想 - 事实上,它甚至不应该是必需的。IClonable

使用 、 支持标准忽略属性。支持复杂的集合、不带 setter 的属性、只读字段等。[IgnoreDataMember][NonSerialized]

我希望它能帮助那些遇到与我相同的问题的其他人。

评论

0赞 Tony 8/29/2021
我刚刚安装了名为 .Clone(),它在这里的 Blazor 项目上运行良好!AnyClone
2赞 Ted Mucuzany 6/21/2019 #42

深度克隆是关于复制状态的。对于状态表示字段.net

假设一个人有一个层次结构:

static class RandomHelper
{
    private static readonly Random random = new Random();

    public static int Next(int maxValue) => random.Next(maxValue);
}

class A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}

class B : A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}

class C : B
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}

克隆可以做到:

static class DeepCloneExtension
{
    // consider instance fields, both public and non-public
    private static readonly BindingFlags bindingFlags =
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    public static T DeepClone<T>(this T obj) where T : new()
    {
        var type = obj.GetType();
        var result = (T)Activator.CreateInstance(type);

        do
            // copy all fields
            foreach (var field in type.GetFields(bindingFlags))
                field.SetValue(result, field.GetValue(obj));
        // for every level of hierarchy
        while ((type = type.BaseType) != typeof(object));

        return result;
    }
}

演示1

Console.WriteLine(new C());
Console.WriteLine(new C());

var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

结果:

C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17

Image: C.random = 96 B.random = 18 A.random = 46

C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18

Clone: C.random = 96 B.random = 18 A.random = 46

C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79

请注意,所有新对象都有 field 的随机值,但与randomcloneimage

演示2

class D
{
    public event EventHandler Event;
    public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}

// ...

var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");

image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");

image.RaiseEvent();
image.RaiseEvent();

var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");

clone.RaiseEvent();
image.RaiseEvent();

结果:

Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728

请注意,事件支持字段也被复制,客户端也订阅了克隆的事件。

评论

0赞 Ted Mucuzany 7/4/2019
确实如此。它会引起棘手的副作用,因此应谨慎使用。
1赞 Konrad 6/28/2019 #43

用:System.Text.Json

https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/

public static T DeepCopy<T>(this T source)
{
    return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));
}

新 API 正在使用 .这应该很快,做一些基准测试会很好。Span<T>

注意:Json.NET 中不需要 like,因为它将默认替换集合值。您现在应该忘记 Json.NET,因为一切都将被新的官方 API 取代。ObjectCreationHandling.Replace

我不确定这是否适用于私有字段。

28赞 alelom 7/8/2019 #44

DeepCloner:快速、简单、有效的 NuGet 包,用于解决克隆问题

在阅读了所有答案后,我很惊讶没有人提到这个优秀的包:

DeepCloner GitHub 项目

DeepCloner NuGet 包

详细阐述一下它的自述文件,以下是我们在工作中选择它的原因:

  • 它可以深拷贝或浅拷贝
  • 在深度克隆中,所有对象图都会被维护。
  • 在运行时使用代码生成,因为结果克隆速度极快
  • 对象由内部结构复制,未调用任何方法或 ctor
  • 您不需要以某种方式标记类(如 Serializable-attribute,或实现接口)
  • 无需指定克隆的对象类型。对象可以转换为接口或作为抽象对象(例如,您可以将 int 数组克隆为 abstract Array 或 IEnumerable;甚至可以克隆 null 而不会出现任何错误)
  • 被克隆的对象没有任何能力来确定他是克隆的(除非使用非常具体的方法)

用法:

var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();

性能:

README 包含各种克隆库和方法的性能比较:DeepCloner 性能

要求:

  • .NET 4.0 或更高版本或 .NET Standard 1.3 (.NET Core)
  • 需要“完全信任”权限集或“反射”权限 (MemberAccess)

评论

3赞 EduLopez 5/19/2020
这个问题已经很老了。我认为这个答案应该上升,这样人们才能真正看到这里的价值。
0赞 Alexandru Dicu 10/28/2021
用于克隆对象的额外包引用?不太好。
2赞 alelom 10/28/2021
然后,请随意实施此线程中提出的百万个解决方案之一。我发现这个包是一个非常方便的解决方案。我只希望 MS 能在 C# 或 .NET 中嵌入与此等效的解决方案。
1赞 Aaron Haspel 4/27/2022
我曾经做过自定义克隆,就像原来的提问者一样,但这个包,与各种序列化/反序列化解决方案不同,它的速度同样快,开箱即用。我也不喜欢额外的包引用,但对我来说,这是值得的。
0赞 aca 2/2/2023
我试过这个,但我想克隆的模型有问题,并意识到它与模型继承的接口有关。不得不放弃,寻找另一个解决方案,但对这个真的很乐观。
13赞 Marcell Toth 7/31/2019 #45

免责声明:我是上述软件包的作者。

令我惊讶的是,2019 年这个问题的顶级答案仍然使用序列化或反射。

序列化是有限制的(需要属性、特定的构造函数等),并且非常慢

BinaryFormatter需要属性,需要无参数构造函数或属性,两者都不能很好地处理只读字段或接口,并且两者都比必要的慢 10-30 倍。SerializableJsonConverter

表达式树

您可以改用表达式树Reflection.Emit 仅生成一次克隆代码,然后使用该编译的代码,而不是缓慢的反射或序列化。

我自己遇到了这个问题,但没有看到令人满意的解决方案,我决定创建一个包,它只做这个,适用于每种类型,并且几乎与自定义编写的代码一样快

您可以在 GitHub 上找到该项目:https://github.com/marcelltoth/ObjectCloner

用法

可以从 NuGet 安装它。获取包并将其用作:ObjectCloner

var clone = ObjectCloner.DeepClone(original);

或者,如果您不介意用扩展来污染您的对象类型,请也获取并编写:ObjectCloner.Extensions

var clone = original.DeepClone();

性能

克隆类层次结构的简单基准测试显示,性能比使用 Reflection 快 ~3 倍,比 Newtonsoft.Json 序列化快 ~12 倍,比强烈建议的 .BinaryFormatter

评论

0赞 JamesHoux 7/5/2020
序列化在 2019 年仍然流行的原因是代码生成仅在受信任的环境中工作。这意味着它不能在 Unity 或 iOS 中工作,而且可能永远不会。因此,代码生成是不可移植的。
0赞 Ramil Aliyev 007 1/16/2021
我使用了 NewtonSoft 的 12.0.3 版本,我的类没有参数构造函数,它对我有用
1赞 Meer 5/30/2021
不错的包装,我今天开始使用它。我注意到的一件事,命名空间和类名是相同的,所以要使用类的静态方法,尽管使用了指令,我也必须明确地来自命名空间,例如 - 。ObjectClonerObjectCloner.ObjectCloner.DeepClone(someObject)
5赞 Erçin Dedeoğlu 11/21/2019 #46

最短的方法,但需要依赖:

using Newtonsoft.Json;
    public static T Clone<T>(T source) =>
        JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
0赞 Hidayet R. Colkusu 5/24/2020 #47

对于克隆过程,可以先将对象转换为字节数组,然后再转换回对象。

public static class Extentions
{
    public static T Clone<T>(this T obj)
    {
        byte[] buffer = BinarySerialize(obj);
        return (T)BinaryDeserialize(buffer);
    }

    public static byte[] BinarySerialize(object obj)
    {
        using (var stream = new MemoryStream())
        {
            var formatter = new BinaryFormatter(); 
            formatter.Serialize(stream, obj); 
            return stream.ToArray();
        }
    }

    public static object BinaryDeserialize(byte[] buffer)
    {
        using (var stream = new MemoryStream(buffer))
        {
           var formatter = new BinaryFormatter(); 
           return formatter.Deserialize(stream);
        }
    }
}

必须对对象进行序列化,才能进行序列化过程。

[Serializable]
public class MyObject
{
    public string Name  { get; set; }
}

用法:

MyObject myObj  = GetMyObj();
MyObject newObj = myObj.Clone();
10赞 Sean McAvoy 6/25/2020 #48

创建扩展:

public static T Clone<T>(this T theObject)
{
    string jsonData = JsonConvert.SerializeObject(theObject);
    return JsonConvert.DeserializeObject<T>(jsonData);
}

并这样称呼它:

NewObject = OldObject.Clone();
1赞 Ogglas 3/9/2021 #49

对@Konrad的补充,并使用内置的 @craastadSystem.Text.Json.NET >5

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-5-0

方法:

public static T Clone<T>(T source)
{
    var serialized = JsonSerializer.Serialize(source);
    return JsonSerializer.Deserialize<T>(serialized);
}

扩展方法:

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonSerializer.Serialize(source);
        return JsonSerializer.Deserialize<T>(serialized);
    }
}

评论

0赞 Ogglas 3/9/2021
如果您投反对票,请添加评论原因,否则很难改进答案。
1赞 Adel Tabareh 5/5/2021 #50

使用 System.Text.Json;

public static class CloneExtensions
{
    public static T Clone<T>(this T cloneable) where T : new()
    {
        var toJson = JsonSerializer.Serialize(cloneable);
        return JsonSerializer.Deserialize<T>(toJson);
    }
}
5赞 Izzy 8/5/2021 #51

C# 9.0 引入了需要 (感谢 Mark Nading) 的关键字。这应该允许非常简单的对象克隆(如果需要,还可以进行突变),使用很少的样板,但只能使用 .withrecordrecord

你似乎无法通过将类放入泛型中来克隆(按值)类record;

using System;
                
public class Program
{
    public class Example
    {
        public string A { get; set; }
    }
    
    public record ClonerRecord<T>(T a)
    {
    }

    public static void Main()
    {
        var foo = new Example {A = "Hello World"};
        var bar = (new ClonerRecord<Example>(foo) with {}).a;
        foo.A = "Goodbye World :(";
        Console.WriteLine(bar.A);
    }
}

这写了“Goodbye World :(”- 字符串是通过引用复制的(不需要的)。https://dotnetfiddle.net/w3IJgG

(令人难以置信的是,以上内容在 !https://dotnetfiddle.net/469NJvstruct)

但是克隆似乎确实可以缩进,按值克隆。record

using System;

public class Program
{
    public record Example
    {
        public string A { get; set; }
    }
    
    public static void Main()
    {
        var foo = new Example {A = "Hello World"};
        var bar = foo with {};
        foo.A = "Goodbye World :(";
        Console.WriteLine(bar.A);
    }
}

这将返回“Hello World”,字符串是按值复制的!https://dotnetfiddle.net/MCHGEL

更多信息可以在博客文章中找到:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/with-expression

评论

1赞 Mark Nadig 8/25/2021
根据我所读到的内容,这仅适用于新的“记录”类型。我们中的一个人真的应该在 .net 小提琴:P中尝试一下
0赞 Izzy 8/26/2021
@MarkNadig我什至没有注意到这一点!看起来使用 a 克隆 a 不起作用 - dotnetfiddle.net/w3IJgG;但是,克隆公寓似乎确实是按价值复制的!dotnetfiddle.net/MCHGELrecordclassrecord
1赞 Cinorid 9/16/2021 #52

我对当前的答案做了一些基准测试,发现了一些有趣的事实。

使用 BinarySerializer => https://stackoverflow.com/a/78612/6338072

使用 XmlSerializer => https://stackoverflow.com/a/50150204/6338072

使用 Activator.CreateInstance => https://stackoverflow.com/a/56691124/6338072

这些是结果

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1734 (1909/November2019Update/19H2)

Intel Core i5-6200U CPU 2.30GHz (Skylake),1 个 CPU,4 个逻辑内核和 2 个物理内核 [主机]:.NET Framework 4.8 (4.8.4400.0),x86 LegacyJIT DefaultJob:.NET Framework 4.8 (4.8.4400.0),x86 LegacyJIT

方法 意味 着 错误 标准开发 第 0 代 分配
BinarySerializer 二进制序列化程序 220.69 微秒 4.374 微秒 9.963 微秒 49.8047 77
XmlSerializer (英语) 182.72 微秒 3.619 微秒 9.405 微秒 21.9727 34 KB
Activator.CreateInstance 49.99 微秒 0.992 微秒 2.861 微秒 1.9531 3 KB
-1赞 ʞᴉɯ 10/7/2021 #53

找到了这个包,与它相比,它似乎更快,并且没有依赖关系。DeepCloner

https://github.com/AlenToma/FastDeepCloner

3赞 Vivek Nuna 1/29/2022 #54

我将使用以下简单的方法来实现这一点。 只需创建一个抽象类并实现方法,即可再次序列化和反序列化并返回。

public abstract class CloneablePrototype<T>
{
    public T DeepCopy()
    {
        string result = JsonConvert.SerializeObject(this);
        return JsonConvert.DeserializeObject<T>(result);
    }
}
public class YourClass : CloneablePrototype< YourClass>
…
…
…

并像这样使用它来创建深度副本。

YourClass newObj = (YourClass)oldObj.DeepCopy();

如果您还需要实现浅拷贝方法,此解决方案也很容易扩展。

只需在抽象类中实现一个新方法即可。

public T ShallowCopy()
{
    return (T)this.MemberwiseClone();
}
2赞 David Oganov 4/14/2022 #55

除了这里的一些精彩答案之外,在 C# 9.0 及更高版本中,您可以执行以下操作(假设您可以将类转换为记录):

record Record
{
    public int Property1 { get; set; }

    public string Property2 { get; set; }
}

然后,只需使用 with 运算符将一个对象的值复制到新对象。

var object1 = new Record()
{
    Property1 = 1,
    Property2 = "2"
};

var object2 = object1 with { };
// object2 now has Property1 = 1 & Property2 = "2"

我希望这对:)有所帮助

1赞 Efreeto 8/4/2022 #56

基于 @craastad 的答案,适用于派生类。

在原始答案中,如果调用方正在调用基类对象,则克隆的对象属于基类。但以下代码将返回派生类。DeepCopy

using Newtonsoft.Json;

public static T DeepCopy<T>(this T source)
{
    return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), source.GetType());
}
-1赞 Adamy 8/10/2022 #57

如果使用 net.core 并且对象是可序列化的,则可以使用

var jsonBin = BinaryData.FromObjectAsJson(yourObject);

然后

var yourObjectCloned = jsonBin.ToObjectFromJson<YourType>();

BinaryData 位于 dotnet 中,因此不需要第三方库。它还可以处理类上的属性是 Object 类型的情况(属性中的实际数据仍然需要可序列化)

5赞 Daniel Jonsson 9/2/2022 #58

在我正在使用的代码库中,我们有一个来自 GitHub 项目 Burtsev-Alexey/net-object-deep-copy 的文件副本。它已经 9 岁了。它奏效了,尽管我们后来意识到对于较大的对象结构来说,它非常慢。ObjectExtensions.cs

相反,我们在 GitHub 项目 jpmikkers/Baksteen.Extensions.DeepCopy 中找到了该文件的分支。以前需要大约 30 分钟才能完成大型数据结构的深度复制操作,现在感觉几乎是瞬间完成的。ObjectExtensions.cs

此改进版本包含以下文档:

用于快速对象克隆的 C# 扩展方法。

这是阿列克谢·布尔采夫(Alexey Burtsev)的深度复印机的速度优化分叉。根据您的用例,这将比原始速度快 2 到 3 倍。它还修复了原始代码中存在的一些错误。与经典的二进制序列化/反序列化深度克隆技术相比,此版本的速度大约快七倍(对象包含的数组越多,加速系数就越大)。

加速是通过以下技术实现的:

  • 缓存对象反射结果
  • 不要深度复制原语或不可变的结构和类(例如枚举和字符串)
  • 为了提高参考的局部性,在内部循环中处理“快速”维度或多维数组
  • 使用已编译的 lamba 表达式调用 MemberwiseClone

如何使用:

using Baksteen.Extensions.DeepCopy;
...
var myobject = new SomeClass();
...
var myclone = myobject.DeepCopy()!;    // creates a new deep copy of the original object 

注意:仅当在项目中启用了可为 null 的引用类型时,才需要感叹号(空容错运算符)

0赞 user756037 6/26/2023 #59

这是一种使用匿名或对象实例进行克隆的方法

   public static T CloneJson<T>(T source)
{
    if (ReferenceEquals(source, null))
    {
        return default;
    }

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
}

private object Clone(object instance)
{
    Type type = instance.GetType();
    MethodInfo genericMethod = this.GetType().GetMethod(nameof(CloneJson));
    MethodInfo cloneMethod = genericMethod.MakeGenericMethod(type);
    object clone = cloneMethod.Invoke(null, new object[] { instance });
    return clone;
}