深度克隆对象


2226

我想做类似的事情:

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

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

我通常不需要此功能,因此在必要时,我会先创建一个新对象,然后分别复制每个属性,但是它总是让我感到有更好或更优雅的处理方式情况。

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


81
可能有用:“为什么复制对象是一件可怕的事情?” agiledeveloper.com/articles/cloning072002.htm
Pedro77


18
您应该看一下AutoMapper
Daniel Little

3
您的解决方案要复杂得多,我迷路了……呵呵。我正在使用DeepClone界面。公共接口IDeepCloneable <T> {T DeepClone(); }
Pedro77

1
@ Pedro77-尽管有趣的是,该文章最后说要clone在类上创建一个方法,然后让它调用内部的私有构造函数,该构造函数将被传递this。因此,复制非常麻烦,但仔细复制(绝对值得一读)则不是。; ^)
鲁芬2014年

Answers:


1715

虽然标准做法是实现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);
        }
    }
}

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

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

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

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

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

编辑(2015年1月10日)以为我会重新考虑这一点,要说我最近开始使用(Newtonsoft)Json来做到这一点,它应该更轻巧,并且避免了[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);
}

24
stackoverflow.com/questions/78536/cloning-objects-in-c/…有指向上面代码的链接[并且引用了其他两个这样的实现,其中一个在我的上下文中更合适]
Ruben Bartelink 09年

102
序列化/反序列化涉及不必要的大量开销。请参见C#中的ICloneable接口和.MemberWise()克隆方法。
3Dave 2010年

18
@David,理所当然,但是如果对象很轻,并且使用时对性能的影响不能满足您的要求,那么这是一个有用的技巧。我承认,我并没有在一个循环中大量使用大量数据,但我从未见过任何有关性能的问题。
johnc

16
@Amir:实际上,typeof(T).IsSerializable如果已使用[Serializable]属性标记了类型,则no:也为true 。它不必实现ISerializable接口。
Daniel Gehriger 2011年

11
只是以为我提到了这种方法虽然有用,而且我已经使用了很多次,但它与Medium Trust根本不兼容-因此请注意是否要编写需​​要兼容性的代码。BinaryFormatter访问私有字段,因此无法在部分信任环境的默认权限集中使用。您可以尝试使用另一个序列化程序,但是请确保您的调用者知道,如果传入对象依赖于私有字段,则克隆可能不是完美的。
Alex Norcliffe

298

我想要一个克隆器,用于主要是基元和列表的非常简单的对象。如果您的对象是开箱即用的JSON可序列化的,则此方法可以解决问题。这不需要修改或实现克隆类上的接口,只需要像JSON.NET这样的JSON序列化程序即可。

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

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

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

13
该solutiojn甚至快于BinaryFormatter的解决方案,.NET序列化性能对比
esskar

3
谢谢你 我能够使用MongoDB C#驱动程序附带的BSON序列化程序执行基本相同的操作。
Mark Ewer 2014年

3
这对我来说是最好的方法,但是,我使用的Newtonsoft.Json.JsonConvert却是相同的
Pierre

1
为此,要克隆的对象需要像已提到的那样可序列化-这还意味着例如它可能没有循环依赖关系
radomeit

2
我认为这是最好的解决方案,因为该实现可以应用于大多数编程语言。
mr5

178

不使用的原因ICloneable不是因为它没有一个通用的接口。 不使用它的原因是因为它含糊不清。不清楚是要浅拷贝还是要深拷贝。这取决于实施者。

是的,MemberwiseClone进行浅表复制,但与之相反的MemberwiseClone不是Clone;也许DeepClone不存在。通过对象的ICloneable接口使用对象时,您不知道基础对象执行哪种克隆。(而且XML注释也不清楚,因为您将获得接口注释,而不是对象的Clone方法中的接口注释。)

我通常所做的只是简单地创建一种Copy完全符合我想要的方法。


我不清楚为什么ICloneable被认为是模糊的。给定类似Dictionary(Of T,U)的类型,我希望ICloneable.Clone应该执行必要的深浅复制级别,以使新字典成为包含相同T和U(结构内容,和/或对象引用)。歧义在哪里?可以肯定的是,继承了ISelf(Of T)(包括“ Self”方法)的通用ICloneable(Of T)会好得多,但我认为深度克隆与浅层克隆没有歧义。
supercat

31
您的示例说明了该问题。假设您有一个Dictionary <string,Customer>。克隆的词典是否应具有原始客户相同的客户对象,或这些客户对象的副本?有一种合理的用例。但是,ICloneable并不清楚你会得到哪一个。这就是为什么它没有用的原因。
Ryan Lundy

@Kyralessa Microsoft MSDN文章实际上指出了这个问题,即不知道您是要深拷贝还是浅拷贝。
暗恋

来自重复的stackoverflow.com/questions/129389/…的答案描述了复制扩展,基于递归MembershipClone
Michael Freidgeim,

123

在对这里链接的许多选项以及该问题的可能解决方案进行了很多阅读之后,我相信所有选项在Ian P的链接上都得到了很好的总结(所有其他选项都是这些选项的变体),并且最佳解决方案是Pedro77在问题评论上的链接

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

在C Sharp中克隆对象最好的办法!

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

由表达式树文章快速深复制 有也被序列化,反射和表达式树克隆的性能对比。

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

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

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

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

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

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

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

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

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

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

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

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

产生的输出将是:

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

观察到,如果我们对对象数量进行计数,则此处实现的克隆将对对象数量进行正确计数。


6
MS建议不要将其ICloneable用于公共成员。“由于Clone的调用者不能依赖于执行可预测的克隆操作的方法,因此我们建议不要在公共API中实现ICloneable。” msdn.microsoft.com/zh-cn/library/…但是,基于Venkat Subramaniam在您的链接文章中给出的解释,我认为在这种情况下使用是有意义的,只要ICloneable对象的创建者具有深厚的知识。了解哪些属性应该是深层副本还是浅层副本(即,深层副本Brain,浅层副本City)
BateTech 2015年

首先,我不是这个主题(公共API)的专家。我认为,MS的说法很有意义。而且我认为假设该API 的用户会有如此深刻的理解是不安全的。因此,只有对于将要使用它的人来说真的无关紧要,才在公共API上实现它才有意义。我猜想使用某种UML非常明确地对每个属性进行区分会有所帮助。但我想听听有更多经验的人的来信。:P
cregox

您可以使用CGbR克隆生成器并获得类似的结果,而无需手动编写代码。
Toxantron

中级语言的实现很有用
Michael Freidgeim '18

在C#中有没有最后
康拉德·

84

与克隆相比,我更喜欢复制构造函数。意图更加明确。


5
.Net没有副本构造函数。
Pop Catalin's

48
可以确定的是:new MyObject(objToCloneFrom)只需声明一个ctor即可将要克隆的对象作为参数。
尼克

30
不一样 您必须将其手动添加到每个类中,甚至不知道是否要获取深层副本。
Dave Van den Eynde 09年

15
+1为复制ctor。您还必须为每种类型的对象手动编写一个clone()函数,并且当您的类层次结构深入几个层次时,祝您好运。
Andrew Grant

3
使用复制构造函数,您虽然会失去层次结构。agiledeveloper.com/articles/cloning072002.htm
威尔

41

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

15
不幸的是,这是有缺陷的。这等效于调用objectOne.MyProperty = objectTwo.MyProperty(即,它将只是复制引用)。它不会克隆属性的值。
亚历克斯·诺克利夫

1
致亚历克斯·诺克利夫(Alex Norcliffe):问题的作者问到“复制每个属性”而不是克隆。在大多数情况下,不需要精确的属性重复。
康斯坦丁·萨拉瓦托夫

1
我考虑使用这种方法,但递归。因此,如果属性的值是引用,请创建一个新对象,然后再次调用CopyTo。我只是看到一个问题,所有使用的类都必须具有不带参数的构造函数。有人尝试过吗?我也想知道这是否真的可以与包含.net类的属性(如DataRow和DataTable)一起使用?
Koryu

33

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

如何使用它?

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

var newInstance = source.GetClone();

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

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

可以克隆什么?

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

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

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

有多快?

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

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

和更多...

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

的示例表达式调试清单List<int>

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


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

}

与以下C#代码具有相同的含义:

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

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

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

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

    return target;
}

是不是很像您将如何编写自己的Clone方法List<int>


2
NuGet获得这种机会的机会是什么?看来是最好的解决方案。与NClone相比如何?
暗恋

我认为这个答案应该被多次投票。手动实现ICloneable既繁琐又容易出错,如果性能很重要并且需要在短时间内复制成千上万个对象,则使用反射或序列化会很慢。
nightcoder

完全没有,您对反射不正确,您应该只适当地缓存它。在下面检查我的答案stackoverflow.com/a/34368738/4711853
罗姆·波罗多夫(Roman Borodov)2015年

31

我在Silverlight中使用ICloneable时遇到了问题,但是我喜欢序列化的想法,因为我可以序列化XML,所以我这样做了:

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

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

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

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

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

        return sbTR.ToString();
    }

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

31

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

MyObject oldObj; // The existing object to clone

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

使用这种方法,你不必执行ISerializableICloneable在你的对象。这在MVC / MVVM模式中很常见,因此已经创建了这样的简单工具。

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


25

最好是实现一个扩展方法一样

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

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

var copy = anyObject.DeepClone();

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

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

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


使用已发布codeproject.com/Articles/1111658/…的表达式树克隆代码,但由于.NET Framework的新版本失败,出现安全异常,操作可能会破坏运行时的稳定性,这基本上是异常,这是由于表达式树格式不正确,它用于在运行时生成Func,请检查您是否有解决方案。实际上,我仅看到具有深层次结构的复杂对象的问题,简单的对象很容易被复制
Mrinal Kamboj

1
ExpressionTree实现似乎非常好。它甚至可以与循环引用和私有成员一起使用。不需要属性。我找到的最佳答案。
N73k

最好的答案,效果很好,您救了我的

23

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

有关使用ICloneable进行克隆的更详细说明,请参阅本文

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

有关更多选择(请参阅Ian),请参阅此开发人员专栏文章


1
ICloneable没有通用接口,因此不建议使用该接口。
卡格

您的解决方案一直有效,直到需要处理循环引用,然后事情开始复杂化为止,最好尝试使用深度序列化来实现深度克隆。
Pop Catalin's

不幸的是,并非所有对象都可以序列化,因此您也不能总是使用该方法。到目前为止,Ian的链接是最全面的答案。
Zach Burlingame

19
  1. 基本上,您需要实现ICloneable接口,然后实现对象结构复制。
  2. 如果它是所有成员的完整副本,则需要确保(与选择的解决方案无关)所有子代也都是可克隆的。
  3. 有时,您需要在此过程中意识到一些限制,例如,如果您复制ORM对象,则大多数框架仅允许将一个对象附加到会话,并且您不得对该对象进行克隆,或者是否有可能需要注意关于这些对象的会话附加。

干杯。


4
ICloneable没有通用接口,因此不建议使用该接口。
卡格

简单明了的答案是最好的。
DavidGuaita

17

编辑:项目已终止

如果您想将克隆真正克隆为未知类型,可以看看 fastclone

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

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

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


这似乎非常有用
LuckyLikey

从一个代码快照开始工作比整个系统(特别是封闭系统)要容易得多。完全可以理解,没有一个库可以一口气解决所有问题。应该放松一下。
TarmoPikaro

1
我已经尝试了您的解决方案,但效果很好,谢谢!我认为这个答案应该被多次投票。手动实现ICloneable既繁琐又容易出错,如果性能很重要并且需要在短时间内复制成千上万个对象,则使用反射或序列化会很慢。
nightcoder

我尝试了一下,但对我来说根本没有用。引发MemberAccess异常。
迈克尔·布朗

它不适用于较新版本的.NET,并且已停产
Michael Sander

14

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

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

现在,目标对象是源对象的副本。还不够简单?创建一个扩展方法以在解决方案中的任何地方使用:

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

扩展方法可以如下使用:

MyType copy = source.Copy();

小心这一点,它的性能确实很差。我最终切换到johnc答案,该答案很短,并且表现更好。
Agorilla

1
这只会进行浅表复制。
N73k

11

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

我用这个:

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

在另一个地方:

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

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

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

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

10

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

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

换句话说,除非您有需要解决的性能瓶颈,并且可以通过探查器证明它,否则请选择另一个答案。

比其他方法快10倍

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

  • 比任何涉及序列化/反序列化的速度快10倍;
  • NET能够达到的理论最高速度还算不错。

和方法...

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

请注意,如果您将Nested MemberwiseClone用于深层副本,则必须为类中的每个嵌套级别手动实现ShallowCopy,并必须通过DeepCopy调用所有上述ShallowCopy方法来创建完整的副本。这很简单:总共只有几行,请参见下面的演示代码。

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

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

在类上使用Nested MemberwiseClone几乎与复制结构一样快,并且复制结构的速度与.NET所能达到的理论最大速度相当。

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

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

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

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

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

然后,从main调用演示:

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

再次,请注意,如果您将Nested MemberwiseClone用于深层副本,则必须为该类中的每个嵌套级别手动实现ShallowCopy,并必须通过DeepCopy调用所有上述ShallowCopy方法来创建完整的副本。这很简单:总共只有几行,请参见上面的演示代码。

值类型与引用类型

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

  • 如果您有一个“ struct ”,它是一个值类型,因此您可以复制它,并且内容将被克隆(但是除非您使用本文中的技巧,否则它只会进行浅表克隆)。
  • 如果您有一个“ ”,则它是一个引用类型,因此,如果您复制它,那么您所做的就是复制指向它的指针。要创建真正的克隆,您必须更具创造力,并使用值类型和引用类型之间的差异,这会在内存中创建原始对象的另一个副本。

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

校验和以帮助调试

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

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

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

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

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

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

更新资料

显然,ExpressMapper的速度甚至比上述手工编码还快。我可能必须看一下它们与探查器的比较。


如果复制结构,则会得到浅表副本,对于深表副本,可能仍需要特定的实现。
Lasse V. Karlsen

@Lasse V.卡尔森。是的,您是完全正确的,我已经更新了答案以使其更加清楚。此方法可用于制作结构类的深层副本。您可以运行包含的示例演示代码来演示其完成方式,其中包含一个深度克隆嵌套结构的示例,另一个深度克隆嵌套类的示例。
Contango

9

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

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


ICloneable没有通用接口,因此不建议使用该接口。
卡格

8

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


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

2
这看起来像逐成员克隆,因为它不知道引用类型的属性
sll

1
如果您想要令人目眩的快速性能,请不要执行此实现:它使用反射,所以不会那么快。相反,“过早的优化是万恶之源”,因此在运行分析器之前,请不要考虑性能方面的问题。
Contango 2011年

1
未定义CreateInstanceOfType吗?
MonsterMMORPG,2015年

在进行整数运算时失败:“非静态方法需要目标。”
Mr.B

8

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

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

7

代码生成器

从序列化到手动实现再到反思,我们已经看到了很多想法,我想提出一种使用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;
    }
}

注意:最新版本具有更多的null检查功能,但为了更好的理解,我省略了它们。


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

如果您还有其他要复制的内容,请添加它们


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


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

编辑:要求

    using System.Linq;
    using System.Reflection;

那就是我的使用方式

public MyClass Clone(MyClass theObjectIneededToClone)
{
    MyClass clonedObj = theObjectIneededToClone.DeepClone();
}

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


5

我想你可以试试看。

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

4

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

} 

4

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

如果您将其正确缓存,则它将深度克隆1,000000对象4,6s(由Watcher测量)。

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

比您采用缓存的属性或将新属性添加到字典并简单地使用它们

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

完整代码检查我的帖子中的另一个答案

https://stackoverflow.com/a/34365709/4711853


2
调用prop.GetValue(...)仍然是反射,不能被缓存。在一个表达式树中,它的编译速度如此之快
Tseng

4

由于此问题的几乎所有答案都不能令人满意,或者在我的情况下根本不起作用,因此我编写了AnyClone,该代码完全通过反思实现,并解决了此处的所有需求。我无法使序列化在结构复杂的复杂场景中工作,并且IClonable不尽人意-实际上,甚至没有必要。

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

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


4

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

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

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

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

表达树

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

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

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

用法

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

var clone = ObjectCloner.DeepClone(original);

或者如果您不介意使用扩展名污染对象类型,请输入以下内容ObjectCloner.Extensions

var clone = original.DeepClone();

性能

一个简单的克隆类层次结构的基准测试表明,性能比使用Reflection快3倍,比Newtonsoft.Json快12倍,比高度推荐的Json序列快36倍BinaryFormatter

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.