深度克隆对象

Nak*_*nch 2135 .net c# clone

我想做的事情如下:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();
Run Code Online (Sandbox Code Playgroud)

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

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

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

joh*_*hnc 1658

虽然标准的做法是实现ICloneable界面(这里描述,所以我不会反刍),这是我在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.", nameof(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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们的想法是将对象序列化,然后将其反序列化为新对象.好处是,当对象过于复杂时,您不必担心克隆所有内容.

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

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

public static T Clone<T>(this T source)
{
   //...
}
Run Code Online (Sandbox Code Playgroud)

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

编辑(2015年1月10日)以为我会重新考虑这一点,提到我最近开始使用(Newtonsoft)Json这样做,它应该更轻,并避免[Serializable]标签的开销.(注意 @atconway在评论中指出私有成员不使用JSON方法克隆)

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

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

    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
Run Code Online (Sandbox Code Playgroud)

  • 序列化/反序列化涉及不必要的显着开销.请参阅C#中的ICloneable接口和.MemberWise()克隆方法. (97认同)
  • http://stackoverflow.com/questions/78536/cloning-objects-in-c/78551#78551有一个上面代码的链接[并引用其他两个这样的实现,其中一个在我的上下文中更合适] (24认同)
  • @David,授予,但如果对象很轻,并且使用它时的性能不是太高,不符合您的要求,那么这是一个有用的提示.我承认,我没有在循环中大量使用大量数据,但我从未见过单一的性能问题. (18认同)
  • @Amir:实际上,没有:`typeof(T).IsSerializable`如果类型已经用`[Serializable]属性标记,也是如此.它不必实现`ISerializable`接口. (16认同)
  • 我想我会提到虽然这种方法很有用,而且我自己多次使用它,但它与Medium Trust完全兼容 - 所以请注意你是否正在编写需要兼容性的代码.BinaryFormatter访问私有字段,因此无法在部分信任环境的默认权限集中工作.您可以尝试另一个序列化程序,但如果传入的对象依赖于私有字段,请确保您的调用者知道克隆可能不完美. (11认同)
  • 仅供参考"Newtonsoft.Json"方法.它不会克隆私人成员.我使用这种方法已经有一段时间了,有人质疑我这个缺点,我测试了它,确定它不会克隆来自源对象的私有成员. (9认同)
  • @johnc我喜欢你的回答,因为它每次都会工作**,但作为一个嵌入式系统人员,以及90年代中期的x86汇编语言图形,我总是在考虑优化.请记住,反序列化涉及昂贵的字符串处理和反射,并且某些属性类型(如字典)无法序列化/反序列化.仍然,我赞成的回应非常好.("只记得"是花生画廊). (7认同)
  • 我只是添加一个并不总是能够访问一个对象来实现ICloneable接口,因此这个解决方案很方便. (6认同)
  • 而不是"if(!typeof(T).IsSerializable)",你可以写"public static T Clone <T>(T source),其中T:ISerializable (5认同)
  • 我建议使用此方法签名:public static T Copy <T>(此T项)其中T:ISerializable (4认同)
  • 如果你要走这条路,使用json序列化程序来避免hazzle无法序列化列表和ienumerables以及所有这些.Json将毫不费力地处理这一切. (4认同)
  • 如果有人遇到克隆值中的接口无法反序列化的相同问题,则可以选择解决它:将TypeNameHandling = TypeNameHandling.Auto添加到序列化和反序列化设置.资料来源:http://stackoverflow.com/a/40403520/3085985 (3认同)
  • @atconway我没有遇到过这个,谢谢,我主要使用这种方法来序列化DTO或网络流量.我会在答案中加上这个限制 (2认同)
  • @ 3Dave之所以到达此职位,是因为我在具有受保护的List &lt;string&gt;成员的对象上使用MemberwiseClone,并且我的原始实例和所得克隆最终都引用了原始实例中的列表。我花了相当长的时间才找出原因。MemberwiseClone似乎正在执行浅表副本,而不是深表副本。 (2认同)
  • BinaryFormatter 不安全,请查看官方文档:https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter?view=net-5.0 (2认同)
  • 关于 BinaryFormatter 弃用的重要说明[此处](/sf/answers/9057681/) (2认同)
  • “BinaryFormatter 序列化方法已过时,并且在 ASP.NET 应用程序中被禁止”。建议的操作 停止在代码中使用 BinaryFormatter。相反,请考虑使用 JsonSerializer 或 XmlSerializer。主要原因与安全相关:https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide (2认同)

cra*_*tad 273

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

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

此外,您可以使用此扩展方法

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 解决方案甚至比BinaryFormatter解决方案更快,[.NET序列化性能比较](http://james.newtonking.com/archive/2010/01/01/net-serialization-performance-comparison) (13认同)
  • 谢谢你.我能够用与C#的MongoDB驱动程序一起提供的BSON序列化器做同样的事情. (3认同)
  • 这对我来说是最好的方式,但是,我使用`Newtonsoft.Json.JsonConvert`但是它是一样的 (3认同)
  • 我认为这是最好的解决方案,因为该实现可以应用于大多数编程语言。 (3认同)
  • 为此,要克隆的对象需要可序列化,如前所述 - 这也意味着例如它可能没有循环依赖 (2认同)
  • 在 DeserializeObject() 中,您应该添加其他选项 `new JsonSerializerSettings {ObjectCreationHandling = ObjectCreationHandling.Replace}`,因为默认构造函数可以更改源中不存在的内容 [](/sf/answers/5502871/) (2认同)

Rya*_*ndy 171

不使用的原因ICloneable不是因为它没有一个通用的接口. 不使用它的原因是因为它含糊不清.它不清楚你是否得到浅或副本; 这取决于实施者.

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

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

  • 您的示例说明了问题.假设你有一个Dictionary <string,Customer>.克隆的Dictionary是否应该具有与原始*相同的*Customer对象,或者那些Customer对象的*副本*?任何一个都有合理的用例.但ICloneable并不清楚你会得到哪一个.这就是它没用的原因. (29认同)

cre*_*gox 108

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

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

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

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

" 表达式树快速复制 "一 还对序列化,反射和表达式树的克隆进行了性能比较.

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

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

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

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

通过指定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);
    }
    …
}
Run Code Online (Sandbox Code Playgroud)

现在考虑从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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

产生的输出将是:

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
Run Code Online (Sandbox Code Playgroud)

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

  • MS建议不要将"ICloneable"用于公共成员."因为克隆的调用者不能依赖执行可预测克隆操作的方法,所以我们建议不要在公共API中实现ICloneable." http://msdn.microsoft.com/en-us/library/system.icloneable(v=vs.110).aspx但是,根据Venkat Subramaniam在您的链接文章中给出的解释,我认为使用它是有意义的在这种情况下*只要ICloneable对象的创建者深入了解哪些属性应该是深对比浅拷贝*(即深拷贝脑,浅拷贝城市) (5认同)
  • C#中没有final (2认同)

Nic*_*ick 83

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

  • 确实如此:new MyObject(objToCloneFrom)只需声明一个ctor,它将对象作为参数进行克隆. (48认同)
  • 这不是一回事.您必须手动将其添加到每个班级,并且您甚至不知道您是否正在申请深层复制. (30认同)
  • +1复制ctor.你必须为每种类型的对象手动编写一个clone()函数,并且当你的类层次结构深入几层时,祝你好运. (14认同)
  • .Net没有复制构造函数. (5认同)
  • 使用复制构造函数,您会失去层次结构.http://www.agiledeveloper.com/articles/cloning072002.htm (3认同)
  • Copy Constructor和Clone/Copy手写方法的问题是维护.易于出错的维护.想象一下,有50多个字段的"DataContract".想象一下,然后你去添加一个新字段或字段.它可以成为一个真正快速的集群-f*.您对该类所做的任何更改,您还必须记住对复制构造函数进行更改.这就是为什么最好将复制保留为序列化或反射等.如果C#会添加Deep Copy本身的功能会很好,但我相信他们不会因为定义什么是Deep Copy而没有剪切和干燥 (3认同)
  • @IAbstract:<s>为什么要检查类型?只依靠继承:pastebin.com/XWGQFBhr ... </ s>没关系,在阅读了Will的链接后,我理解你试图表达的问题:类型AbstractBaseType的成员无法通过ABT中的一些拷贝构造函数克隆,他们的副本需要通过他们特定类型的拷贝构造函数来创建... (2认同)
  • 使用构造函数意味着复制/克隆不能成为接口的一部分 (2认同)

Kon*_*tov 40

简单的扩展方法来复制所有公共属性.适用于任何对象,并没有要求类是[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 ) } );
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,这是有缺陷的.它相当于调用objectOne.MyProperty = objectTwo.MyProperty(即,它只会复制引用).它不会克隆属性的值. (12认同)
  • 我考虑使用这种方法,但使用递归。因此,如果属性的值是引用,则创建一个新对象并再次调用 CopyTo。我只看到一个问题,所有使用的类都必须有一个不带参数的构造函数。有人已经尝试过这个了吗?我还想知道这是否真的适用于包含 .net 类(如 DataRow 和 DataTable)的属性? (2认同)

小智 32

好吧,我在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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*zek 31

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

如何使用它?

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

var newInstance = source.GetClone();
Run Code Online (Sandbox Code Playgroud)

您可以选择什么应该被复制sourcenewInstance使用CloningFlags枚举:

var newInstance 
    = source.GetClone(CloningFlags.Properties | CloningFlags.CollectionItems);
Run Code Online (Sandbox Code Playgroud)

什么可以克隆?

  • Primitive(int,uint,byte,double,char等),已知的不可变类型(DateTime,TimeSpan,String)和委托(包括Action,Func等)
  • 可空
  • T []数组
  • 自定义类和结构,包括泛型类和结构.

类/ struct成员在内部克隆:

  • 公共值,而不是只读字段
  • 具有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:
}
Run Code Online (Sandbox Code Playgroud)

}

具有与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;
}
Run Code Online (Sandbox Code Playgroud)

这不是很像你如何编写自己的Clone方法List<int>吗?

  • 有什么机会进入NuGet?这似乎是最好的解决方案.它与[NClone](https://github.com/mijay/NClone)相比如何? (2认同)

Mic*_*Cox 28

如果您已经使用像一个第三方应用程序ValueInjecterAutomapper,你可以这样做:

MyObject oldObj; // The existing object to clone

MyObject newObj = new MyObject();
newObj.InjectFrom(oldObj); // Using ValueInjecter syntax
Run Code Online (Sandbox Code Playgroud)

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

在CodePlex上看到值得深入克隆的解决方案.


Zac*_*ame 21

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

有关使用ICloneable克隆的更详细说明,请查看本文.

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

有关更多选项,请参阅此Developer's Corner文章(归功于Ian).


fra*_*kon 21

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

public static T DeepClone<T>(this T originalObject)
{ /* the cloning code */ }
Run Code Online (Sandbox Code Playgroud)

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

var copy = anyObject.DeepClone();
Run Code Online (Sandbox Code Playgroud)

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

  1. 通过序列化(最短的代码)
  2. 反思 -快5倍
  3. 通过表达树 -快20倍

所有链接的方法都很好,并经过深入测试.

  • 使用您发布的表达式树克隆代码https://www.codeproject.com/Articles/1111658/Fast-Deep-Copy-by-Expression-Trees-C-Sharp?fid=1907758&amp;select=5469139&amp;fr=1&amp;tid=5467411,是较新版本的.Net框架失败并出现安全异常,**操作可能会破坏运行时的稳定性**,这基本上是由于格式错误的表达式树导致的异常,该表达式树用于在运行时生成Func,请检查您是否有一些解决方案。事实上,我只看到过具有深层层次结构的复杂对象的问题,简单的对象很容易被复制 (2认同)
  • ExpressionTree 的实现看起来非常好。它甚至可以与循环引用和私有成员一起使用。不需要任何属性。我找到的最佳答案。 (2认同)
  • @aca 不,我当时找不到解决方案 (2认同)

dim*_*ist 18

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

干杯.

  • ICloneable没有通用接口,因此不建议使用该接口. (4认同)

ale*_*a87 18

DeepCloner:解决克隆的快速、简单、有效的 NuGet 包

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

DeepCloner GitHub 项目

DeepCloner NuGet 包

详细介绍一下它的 README,以下是我们在工作中选择它的原因:

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

用法:

var deepClone = new { Id = 1, Name = "222" }.DeepClone();
var shallowClone = new { Id = 1, Name = "222" }.ShallowClone();
Run Code Online (Sandbox Code Playgroud)

表现:

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

要求:

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

  • 这个问题很老了。我认为这个答案应该上升,这样人们才能真正看到这里的价值。 (2认同)
  • 那么,请随意实施本线程中提出的数百万个解决方案之一。我发现这个包是一个非常方便的解决方案。我只希望 MS 在 C# 或 .NET 中嵌入与此等效的解决方案。 (2认同)

Mic*_*der 16

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

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

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

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


Sta*_*ked 12

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

MyType source = new MyType();
Mapper.CreateMap<MyType, MyType>();
MyType target = Mapper.Map<MyType, MyType>(source);
Run Code Online (Sandbox Code Playgroud)

目标对象现在是源对象的副本.不够简单?创建一个扩展方法,以便在解决方案中的任

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

通过使用扩展方法,三行成为一行:

MyType copy = source.Copy();
Run Code Online (Sandbox Code Playgroud)

  • 这仅进行浅复制。 (3认同)

Dan*_*dor 11

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

我用这个:

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

在另一个地方:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}
Run Code Online (Sandbox Code Playgroud)

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

更好的是,使用通用List <T>克隆:

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


Con*_*ngo 9

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

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

换句话说,除非您有需要修复的性能瓶颈,否则请使用其他答案,并且您可以使用分析器来证明它.

比其他方法快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
Run Code Online (Sandbox Code Playgroud)

要了解如何使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,从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();
}
Run Code Online (Sandbox Code Playgroud)

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

值类型与引用类型

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

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

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

校验和以帮助调试

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

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

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

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

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

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

更新

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


Mar*_*oth 9

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

我很惊讶 2019 年这个问题的最佳答案仍然使用序列化或反射。

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

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

表达式树

您可以改为使用Expression TreesReflection.Emit只生成一次克隆代码,然后使用该编译代码而不是慢速反射或序列化。

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

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

用法

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

var clone = ObjectCloner.DeepClone(original);
Run Code Online (Sandbox Code Playgroud)

或者,如果您不介意使用扩展名污染您的对象类型ObjectCloner.Extensions,请编写:

var clone = original.DeepClone();
Run Code Online (Sandbox Code Playgroud)

表现

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


Hap*_*ude 8

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

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


xr2*_*0xr 8

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


dou*_*ald 8

这是一个深层复制实现:

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这看起来像成员克隆,因为不知道引用类型属性 (2认同)

kal*_*ohn 8

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

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
Run Code Online (Sandbox Code Playgroud)


Jer*_*yal 7

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

private static MyObj DeepCopy(MyObj source)
        {

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

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

        }
Run Code Online (Sandbox Code Playgroud)

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


Sea*_*voy 7

创建扩展:

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

并这样称呼它:

NewObject = OldObject.Clone();
Run Code Online (Sandbox Code Playgroud)


Luc*_*key 6

我喜欢像这样的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));
        }
    }
Run Code Online (Sandbox Code Playgroud)

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


Tox*_*ron 6

代码生成器

我们已经看到了从手动实现到反射的序列化的很多想法,我想使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:最新版本有一个更多的空检查,但我把它们留下来以便更好地理解.


Dan*_* D. 6

这里的解决方案快速而简单,对我有用而无需继续序列化/反序列化.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:要求

    using System.Linq;
    using System.Reflection;
Run Code Online (Sandbox Code Playgroud)

这就是我如何使用它

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}
Run Code Online (Sandbox Code Playgroud)


小智 6

我想你可以试试看。

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = new MyObject(myObj); //DeepClone it
Run Code Online (Sandbox Code Playgroud)


Erç*_*ğlu 6

最短的方法但需要依赖:

using Newtonsoft.Json;
    public static T Clone<T>(T source) =>
        JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
Run Code Online (Sandbox Code Playgroud)


Izz*_*zzy 6

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

您似乎无法通过将类放入泛型中来克隆(按值)类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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

(令人难以置信的是,上面的代码可以正确地使用structhttps://dotnetfiddle.net/469NJv

但克隆 arecord似乎确实可以起到缩进、按值克隆的作用。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


Dan*_*son 6

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

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

这个改进版本有以下文档:

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

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

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

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

如何使用:

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

注意:仅当您在项目中启用了可空引用类型时才需要感叹号(空值宽容运算符)


sup*_*cat 5

跟着这些步骤:

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

为了最大程度地继承通用性,公开公共克隆功能的类应为sealed,但派生自基类,除非缺少克隆,否则基类是相同的。而不是传递显式可克隆类型的变量,而应使用type的参数ICloneable<theNonCloneableType>。这将允许期望的可克隆派生Foo与的可克隆派生一起使用的例程 DerivedFoo,但也允许创建的不可克隆派生Foo


Rom*_*dov 5

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

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

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
Run Code Online (Sandbox Code Playgroud)

比您获取缓存的属性或向字典添加新属性并简单地使用它们

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}
Run Code Online (Sandbox Code Playgroud)

完整代码请查看我在另一个答案中的帖子

/sf/answers/2405599661/

  • 调用 `prop.GetValue(...)` 仍然是反射,无法缓存。不过,在表达式树中它是编译的,所以速度更快 (2认同)

Mic*_*own 5

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

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

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


归档时间:

查看次数:

774668 次

最近记录:

5 年,9 月 前