深度克隆对象

时间:2008-09-17 00:06:28

标签: c# .net clone

我想做类似的事情:

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

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

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

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

50 个答案:

答案 0 :(得分:1593)

虽然标准做法是实现ICloneable界面(描述here,所以我不会反刍),这是我在The Code Project上发现的一个很好的深度克隆对象复印机以前把它加入我们的东西。

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

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// </summary>
public static class ObjectCopier
{
    /// <summary>
    /// Perform a deep Copy of the object.
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

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

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

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

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

如果您更喜欢使用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在评论中指出私有成员未使用JSON方法克隆)

/// <summary>
/// Perform a deep Copy of the object, using Json as a serialisation method. NOTE: Private members are not cloned using this method.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T CloneJson<T>(this T source)
{            
    // Don't serialize a null object, simply return the default for that object
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    // initialize inner objects individually
    // for example in default constructor some list property initialized with some values,
    // but in 'source' these items are cleaned -
    // without ObjectCreationHandling.Replace default constructor values will be added to result
    var deserializeSettings = new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace};

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

答案 1 :(得分:241)

我想要一个非常简单的对象,主要是原始和列表的克隆人。如果你的对象是开箱即用的JSON serializable,那么这个方法就可以了。这不需要修改或实现克隆类上的接口,只需要像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);
    }
}

答案 2 :(得分:162)

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

是的,MemberwiseClone制作浅层副本,但MemberwiseClone的反面不是{​​{1}};它可能是Clone,它不存在。当您通过其ICloneable接口使用对象时,您无法知道底层对象执行哪种克隆。 (并且XML注释不会说清楚,因为您将获得接口注释而不是对象的Clone方法上的注释。)

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

答案 3 :(得分:102)

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

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

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

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

article Fast Deep Copy by Expression Trees还进行了序列化,反射和表达树克隆的性能比较。

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

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

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

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

  

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

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

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

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

public class SkilledPerson extends Person
{
    private String theSkills;
    public SkilledPerson(Brain aBrain, int theAge, String skills)
    {
        super(aBrain, theAge);
        theSkills = skills;
    }
    protected SkilledPerson(SkilledPerson another)
    {
        super(another);
        theSkills = another.theSkills;
    }

    public Object clone()
    {
        return new SkilledPerson(this);
    }
    public String toString()
    {
        return "SkilledPerson: " + super.toString();
    }
}

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

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

产生的输出将是:

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

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

答案 4 :(得分:77)

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

答案 5 :(得分:38)

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

public static void CopyTo( this object S, object T )
{
    foreach( var pS in S.GetType().GetProperties() )
    {
        foreach( var pT in T.GetType().GetProperties() )
        {
            if( pT.Name != pS.Name ) continue;
            ( pT.GetSetMethod() ).Invoke( T, new object[] 
            { pS.GetGetMethod().Invoke( S, null ) } );
        }
    };
}

答案 6 :(得分:30)

我在Silverlight中使用ICloneable时遇到了问题,但我喜欢seralization的想法,我可以将XML封装起来,所以我这样做了:

static public class SerializeHelper
{
    //Michael White, Holly Springs Consulting, 2009
    //michael@hollyspringsconsulting.com
    public static T DeserializeXML<T>(string xmlData) where T:new()
    {
        if (string.IsNullOrEmpty(xmlData))
            return default(T);

        TextReader tr = new StringReader(xmlData);
        T DocItms = new T();
        XmlSerializer xms = new XmlSerializer(DocItms.GetType());
        DocItms = (T)xms.Deserialize(tr);

        return DocItms == null ? default(T) : DocItms;
    }

    public static string SeralizeObjectToXML<T>(T xmlObject)
    {
        StringBuilder sbTR = new StringBuilder();
        XmlSerializer xmsTR = new XmlSerializer(xmlObject.GetType());
        XmlWriterSettings xwsTR = new XmlWriterSettings();

        XmlWriter xmwTR = XmlWriter.Create(sbTR, xwsTR);
        xmsTR.Serialize(xmwTR,xmlObject);

        return sbTR.ToString();
    }

    public static T CloneObject<T>(T objClone) where T:new()
    {
        string GetString = SerializeHelper.SeralizeObjectToXML<T>(objClone);
        return SerializeHelper.DeserializeXML<T>(GetString);
    }
}

答案 7 :(得分:27)

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

如何使用?

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

var newInstance = source.GetClone();

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

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

什么可以克隆?

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

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

  • public,而非readonly字段的值
  • 包含get和set访问器的公共属性的值
  • 实施ICollection的类型的集合项

它的速度有多快?

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

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

以及更多......

详细了解documentation上生成的表达式。

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

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


                         $initializers))
                } .Else {
                    .Break #Label2 { }
                }
            }
            .LabelTarget #Label2:
        }
    } .Else {
        .Default(System.Void)
    };
    .Label
        $target
    .LabelTarget #Label1:
}

}

具有与c#代码相同的含义:

(source, flags, initializers) =>
{
    if(source == null)
        return null;

    if(initializers.ContainsKey(typeof(List<int>))
        target = (List<int>)initializers[typeof(List<int>)].Invoke((object)source);
    else
        target = new List<int>();

    if((flags & CloningFlags.Properties) == CloningFlags.Properties)
    {
        target.Capacity = target.Capacity.GetClone(flags, initializers);
    }

    if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
    {
        var targetCollection = (ICollection<int>)target;
        foreach(var item in (ICollection<int>)source)
        {
            targetCollection.Add(item.Clone(flags, initializers));
        }
    }

    return target;
}

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

答案 8 :(得分:26)

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

MyObject oldObj; // The existing object to clone

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

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

请参阅the valueinjecter deep cloning solution on CodePlex

答案 9 :(得分:20)

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

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

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

请参阅此Developer's Corner article以获取更多选项(归功于Ian)。

答案 10 :(得分:19)

最好是实施扩展方法,如

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

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

var copy = anyObject.DeepClone();

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

  1. By Serialization(最短的代码)
  2. By Reflection - 快5倍
  3. By Expression Trees - 快20倍
  4. 所有相关方法都运作良好并经过深度测试。

答案 11 :(得分:16)

  1. 基本上你需要实现ICloneable接口然后实现对象结构复制。
  2. 如果它是所有成员的深层副本,您需要保证(不与您选择的解决方案相关)所有孩子都可以克隆。
  3. 有时你需要在这个过程中意识到一些限制,例如,如果你复制ORM对象,大多数框架只允许一个对象附加到会话,你不能制作这个对象的克隆,或者如果你有可能需要关心这些对象的会话附加。
  4. 干杯。

答案 12 :(得分:15)

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

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

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

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

答案 13 :(得分:11)

保持简单,并像其他人一样使用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();

答案 14 :(得分:10)

我想出了这个来克服.NET必须手动深度复制List&lt; T&gt;的缺点。

我用这个:

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

在另一个地方:

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

我试图找到执行此操作的oneliner,但由于无法在匿名方法块中工作,因此无法实现。

更好的是,使用通用List&lt; T&gt;克隆器:

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

答案 15 :(得分:7)

这是一个深层复制实现:

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

答案 16 :(得分:7)

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

答案 17 :(得分:7)

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

  • 如果您希望以最快的速度运行,请选择此答案。
  • 如果您想要一种非常简单的克隆方法,请忽略此答案。

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

比其他方法快10倍

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

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

方法......

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

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

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

  • 嵌套结构上嵌套的MemberwiseClone的1.08秒
  • 对于嵌套类,嵌套的MemberwiseClone
  • 4.77秒
  • 39.93秒进行序列化/反序列化

在类上使用嵌套的MemberwiseClone几乎与复制结构一样快,并且复制结构非常接近.NET能够达到的理论最大速度。

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

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

Demo 3 of deep copy, using class and serialize/deserialize:
  Elapsed time: 00:00:39.9339425,30000000

要了解如何使用MemberwiseCopy执行深层复制,以下是用于生成上述时间的演示项目:

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

然后,从main调用demo:

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

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

值类型与引用类型

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

  • 如果你有一个“结构”,它是一个值类型,所以你可以复制它,然后克隆内容(但它只会变浅除非你使用这篇文章中的技巧,否则克隆。)
  • 如果您有“”,则它是引用类型,因此如果您复制它,您所做的只是将指针复制到它。要创建真正的克隆,您必须更具创造性,并使用differences between value types and references types在内存中创建原始对象的另一个副本。

请参阅differences between value types and references types

校验和以帮助调试

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

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

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

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

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

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

更新

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

答案 18 :(得分:7)

通常,您实现ICloneable接口并自己实现Clone。 C#对象有一个内置的MemberwiseClone方法,它执行浅拷贝,可以帮助你解决所有原语。

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

答案 19 :(得分:7)

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

https://github.com/kalisohn/CloneBehave

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

例如:以下代码将为deepClone Address,但仅​​执行_currentJob字段的浅表副本。

public class Person 
{
  [DeepClone(DeepCloneBehavior.Shallow)]
  private Job _currentJob;      

  public string Name { get; set; }

  public Job CurrentJob 
  { 
    get{ return _currentJob; }
    set{ _currentJob = value; }
  }

  public Person Manager { get; set; }
}

public class Address 
{      
  public Person PersonLivingHere { get; set; }
}

Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");

Address adrClone = adr.Clone();

//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true

答案 20 :(得分:6)

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

private static MyObj DeepCopy(MyObj source)
        {

            var DeserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

            return JsonConvert.DeserializeObject<MyObj >(JsonConvert.SerializeObject(source), DeserializeSettings);

        }

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

答案 21 :(得分:5)

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

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

答案 22 :(得分:5)

代码生成器

我们已经看到了很多关于从手动实现到反思的序列化的想法,我想使用CGbR Code Generator提出一种完全不同的方法。生成克隆方法是内存和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;
    }
}

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

答案 23 :(得分:5)

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

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

答案 24 :(得分:4)

请按照以下步骤操作:

  • 定义ISelf<T>,其中包含只返回Self的{​​{1}}属性和T,该属性派生自ICloneable<out T>并包含方法{{ 1}}。
  • 然后定义ISelf<T>类型,该类型对传入的类型实现T Clone()转换CloneBase
  • 每个派生类型都应该通过调用基本克隆方法实现protected virtual generic VirtualClone,然后执行任何操作以正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。

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

答案 25 :(得分:4)

我想你可以尝试一下。

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

答案 26 :(得分:3)

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

需要 System,System.IO,System.Runtime.Serialization,System.Runtime.Serialization.Formatters.Binary,System.Xml ;

public static class ObjectCopier
{

    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]' or '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T Clone<T>(T source)
    {
        if (typeof(T).IsSerializable == true)
        {
            return CloneUsingSerializable<T>(source);
        }

        if (IsDataContract(typeof(T)) == true)
        {
            return CloneUsingDataContracts<T>(source);
        }

        throw new ArgumentException("The type must be Serializable or use DataContracts.", "source");
    }


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[Serializable]'
    /// </summary>
    /// <remarks>
    /// Found on http://stackoverflow.com/questions/78536/cloning-objects-in-c-sharp
    /// Uses code found on CodeProject, which allows free use in third party apps
    /// - http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx
    /// </remarks>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingSerializable<T>(T source)
    {
        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serializable.", "source");
        }

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

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


    /// <summary>
    /// Perform a deep Copy of an object that is marked with '[DataContract]'
    /// </summary>
    /// <typeparam name="T">The type of object being copied.</typeparam>
    /// <param name="source">The object instance to copy.</param>
    /// <returns>The copied object.</returns>
    public static T CloneUsingDataContracts<T>(T source)
    {
        if (IsDataContract(typeof(T)) == false)
        {
            throw new ArgumentException("The type must be a data contract.", "source");
        }

        // ** Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        DataContractSerializer dcs = new DataContractSerializer(typeof(T));
        using(Stream stream = new MemoryStream())
        {
            using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
            {
                dcs.WriteObject(writer, source);
                writer.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max))
                {
                    return (T)dcs.ReadObject(reader);
                }
            }
        }
    }


    /// <summary>
    /// Helper function to check if a class is a [DataContract]
    /// </summary>
    /// <param name="type">The type of the object to check.</param>
    /// <returns>Boolean flag indicating if the class is a DataContract (true) or not (false) </returns>
    public static bool IsDataContract(Type type)
    {
        object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
        return attributes.Length == 1;
    }

} 

答案 27 :(得分:3)

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

令我感到惊讶的是,2019年该问题的主要答案仍然使用序列化或反射。

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

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

表达树

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

我自己遇到问题并没有找到令人满意的解决方案,所以我决定创建一个可以做到这一点的程序包,并且适用于每种类型,并且几乎与自定义编写代码一样快 >

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

用法

您可以从NuGet安装它。要么获取ObjectCloner软件包,然后将其用作:

var clone = ObjectCloner.DeepClone(original);

或者如果您不介意使用扩展名污染对象类型,请同时获取ObjectCloner.Extensions并写:

var clone = original.DeepClone();

性能

克隆类层次结构的简单基准测试显示,性能比使用Reflection快3倍,比Newtonsoft快12倍。Json序列化和推荐的BinaryFormatter快36倍。

答案 28 :(得分:3)

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

的内容
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增长,仍然只会克隆那些已经序列化的字段。

答案 29 :(得分:3)

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

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

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

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

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

yourClass newLine = oldLine.DeepCopy();

希望这会有所帮助。

答案 30 :(得分:2)

创建扩展程序:

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

并这样称呼它:

NewObject = OldObject.Clone();

答案 31 :(得分:2)

由于该问题的几乎所有答案都不能令人满意,或者在我的情况下根本不起作用,因此我撰写了AnyClone,该书完全经过反思,可以解决这里的所有需求。我无法使序列化在结构复杂的复杂情况下工作, public IActionResult Login() { var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("iNivDmHLpUA223sqsfhqGbMRdRj1PVkH")); var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); var tokeOptions = new JwtSecurityToken( issuer: "Issuer", audience: "Audience", claims: new List<Claim>() { new Claim("rol", "api_access") }, expires: DateTime.Now.AddMinutes(25), signingCredentials: signinCredentials ); var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions); return Ok(new { Token = tokenString }); } 并不理想-实际上甚至没有必要。

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

我希望它可以帮助遇到我同样问题的其他人。

答案 32 :(得分:1)

当使用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自己的格式化程序来序列化对象。

答案 33 :(得分:1)

C#扩展支持&#34;不支持 ISerializable &#34;类型也是。

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

答案 34 :(得分:1)

深度克隆是关于复制状态。对于.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;
    }
}

Demo1

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

请注意,所有新对象的random字段均具有随机值,但是cloneimage完全匹配

Demo2

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

请注意,事件后备字段也已复制,客户端也已订阅克隆事件。

答案 35 :(得分:1)

最短但需要依赖的方式:

using Newtonsoft.Json;
    public static T Clone<T>(T source) =>
        JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));

答案 36 :(得分:1)

另一个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;
        }
    }
}

答案 37 :(得分:1)

映射器执行深层复制。对象的Foreach成员创建一个新对象并分配其所有值。它在每个非原始内部成员上递归工作。

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

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

答案 38 :(得分:1)

令人难以置信的是,您可以花多少精力使用IClonable界面 - 特别是如果您有繁重的类层次结构。此外,MemberwiseClone以某种方式奇怪地工作 - 它甚至不能完全克隆普通的List类型的结构。

当然,序列化最有趣的两难选择是序列化引用 - 例如您拥有子父关系的类层次结构。 我怀疑二进制序列化器能够在这种情况下帮助你。 (最终会出现递归循环+堆栈溢出)。

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

然而 - 它不支持列表,补充说支持,也考虑到重新育儿。 对于仅限于父母的规则,我已将该字段或属性命名为&#34; parent&#34;,然后DeepClone将忽略该规则。您可能想要为反向引用决定自己的规则 - 对于树层次结构它可能是&#34;左/右&#34;等...

以下是整个代码段,包括测试代码:

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
    }

}

答案 39 :(得分:0)

我找到了一种新方法,即Emit。

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

Emit可以看到official documentGuide

你应该学习一些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更改它太难了,我无法做到。

答案 40 :(得分:0)

通用方法在技术上都是有效的,但是我只是想从我自己身上添加一条注释,因为我们实际上很少需要真正的深层副本,并且我强烈反对在实际的业务应用程序中使用通用深层副本,因为这样做会使您可能在许多地方复制对象然后进行显式修改,这很容易丢失。

在大多数现实情况下,您还希望对复制过程进行尽可能多的粒度控制,因为您不仅要耦合到数据访问框架,而且实际上复制的业务对象很少会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
            };
        }
    }

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

答案 41 :(得分:0)

使用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中的ObjectCreationHandling.Replace,因为它会默认替换集合值。您现在应该忘记Json.NET,因为一切都将被新的官方API取代。

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

答案 42 :(得分:0)

快速,轻松,有效的Nuget软件包来解决克隆问题

阅读完所有答案后,我很惊讶没有人提到这个出色的包装:

https://github.com/force-net/DeepCloner

详细介绍其自述文件,这是我们在工作中选择它的原因:

免责声明-要求:

  • .NET 4.0或更高版本或.NET Standard 1.3(.NET Core)
  • 需要设置“完全信任”权限或“反射”权限(MemberAccess)
  
      
  • 它可以进行深层或浅层复制
  •   
  • 在深度克隆中,将保留所有对象图。
  •   
  • 在运行时使用代码生成,因为克隆速度非常快
  •   
  • 对象是通过内部结构复制的,没有方法或ctor称为
  •   
  • 您不需要以某种方式标记类(例如Serializable-attribute或实现接口)
  •   
  • 无需指定要克隆的对象类型。可以将对象强制转换为接口或抽象对象(例如,您可以将int数组克隆为抽象Array或IEnumerable;甚至可以将null克隆而没有任何错误)
  •   
  • 克隆对象没有任何能力确定他是克隆对象(除非使用非常特殊的方法)
  •   

用法很简单:

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

答案 43 :(得分:0)

如何在方法内重铸 应该基本上调用一个自动复制构造函数

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

似乎对我有用

答案 44 :(得分:0)

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

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

答案 45 :(得分:0)

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

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

答案 46 :(得分:0)

@Konrad 和 @craastad 的补充,使用内置的 System.Text.Json for .NET >5

https://docs.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);
    }
}

答案 47 :(得分:0)

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

答案 48 :(得分:0)

C# 9.0 引入了 with 关键字。这应该允许使用很少的样板进行非常简单的对象克隆(如果需要,还可以进行突变)。 我现在无法访问带有 .net framework 5.0 的 IDE,并且在撰写此答案时,目前不存在 c#9 的 .net core 版本,但我认为以下应该可行。

public MyClass MakeClone(){
    return this with {/* mutators here */};
}

甚至只是

var myclonedobject = myoldobject with {/* mutators */};

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

好心人可以验证一下以上是否可行吗?

答案 49 :(得分:-1)

我知道这个问题和answer在这里待了一段时间并且跟随不是完全回答,而是观察,最近我在检查是否确实没有被克隆的时候遇到过(我不会&#如果我没有,那就是我自己;)当我高兴地复制粘贴@johnc updated answer时。

我只是做了自己的扩展方法(几乎是复制粘贴形式的上述答案):

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!

我完全喜欢:什么是F ...所以我抓住了Newtonsoft.Json Github回购并开始挖掘。 它的结果是:在反序列化恰好只有一个ctor且其参数名称匹配(case insensitive)公共属性名称的类型时,它们将作为那些参数传递给ctor。可以在代码herehere中找到一些线索。

底线

我知道这不是常见的情况,示例代码有点滥用,但是嘿!当我检查是否有任何龙在灌木丛中等待跳出来并咬我屁股时,让我感到惊讶。 ;)