ChatGPT解决这个技术问题 Extra ChatGPT

深度克隆对象

我想做类似的事情:

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

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

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

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

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

2
23 revs, 17 users 56%

一种方法是实现 ICloneable 接口(描述为 here,因此我不会重复),这是我不久前在 The Code Project 上找到的一个很好的深度克隆对象复制器,并将其合并到我们的代码中。如其他地方所述,它要求您的对象是可序列化的。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

用 C 语言克隆对象的最佳方法!

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

手动使用 ICloneable,它是浅层的,不是类型安全的

MemberwiseClone,它使用 ICloneable

使用 Activator.CreateInstance 和递归 MemberwiseClone 进行反射

序列化,正如 johnc 的首选答案所指出的那样

中级语言,我不知道它是如何工作的

扩展方法,例如 Havard Straden 的这个自定义克隆框架

表达式树

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

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

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

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

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

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

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

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

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

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

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

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

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

产生的输出将是:

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

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


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

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


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

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

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

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

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

如何使用它?

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

var newInstance = source.GetClone();

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

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

什么可以克隆?

Primitive(int、uint、byte、double、char 等)、已知的不可变类型(DateTime、TimeSpan、String)和委托(包括 Action、Func 等)

可空的

T[] 数组

自定义类和结构,包括泛型类和结构。

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

公共而非只读字段的值

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

这不是很像您为 List<int> 编写自己的 Clone 方法吗?


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

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

MyObject oldObj; // The existing object to clone

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

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

the ValueInjecter deep cloning sample on GitHub


r
ruffin

好吧,我在 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);
    }
}

C
Community

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

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

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

var copy = anyObject.DeepClone();

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

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

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


使用您已发布 codeproject.com/Articles/1111658/… 的表达式树克隆代码,使用较新版本的 .Net 框架失败并出现安全异常,操作可能会破坏运行时,这基本上是由于表达式树格式错误而导致的异常,用于在运行时生成 Func,请检查您是否有解决方案。事实上,我只看到层次结构较深的复杂对象存在问题,简单的对象很容易被复制
ExpressionTree 的实现似乎非常好。它甚至适用于循环引用和私有成员。不需要属性。我找到的最佳答案。
最佳答案,效果很好,你拯救了我的一天
J
Johann

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

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

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

有关更多选项,请参阅此 Developer's Corner article(感谢 Ian)。


ICloneable 没有通用接口,因此不建议使用该接口。
您的解决方案一直有效,直到它需要处理循环引用,然后事情开始复杂化,最好尝试使用深度序列化实现深度克隆。
不幸的是,也不是所有的对象都是可序列化的,所以你也不能总是使用那个方法。伊恩的链接是迄今为止最全面的答案。
C
Christian Davén

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

干杯。


ICloneable 没有通用接口,因此不建议使用该接口。
简单明了的答案是最好的。
E
Eliahu Aaron

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

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

DeepCloner GitHub project

DeepCloner NuGet package

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

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

用法:

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

表现:

自述文件包含各种克隆库和方法的性能比较:DeepCloner Performance

要求:

.NET 4.0 或更高版本或 .NET Standard 1.3 (.NET Core)

需要完全信任权限集或反射权限 (MemberAccess)


这个问题很老了。我认为这个答案应该上升,这样人们才能真正看到这里的价值。
用于克隆对象的额外包参考?不太好。
然后随意实施此线程中提出的百万个解决方案之一。我发现这个包是一个非常方便的解决方案。我只希望 MS 在 C# 或 .NET 中嵌入与此等效的解决方案。
我曾经像最初的提问者一样进行自定义克隆,但是这个包与各种序列化/反序列化解决方案不同,它的速度非常快并且开箱即用。我也不喜欢额外的包参考,但对我来说这是非常值得的。
M
Michael Sander

编辑:项目已停止

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

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

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

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


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

保持简单并像其他人提到的那样使用 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 答案,它和这个一样短并且表现更好。
这只做一个浅拷贝。
H
HappyDude

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

对于深拷贝,它无法知道如何自动完成。


ICloneable 没有通用接口,因此不建议使用该接口。
P
Peter Mortensen

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

我用这个:

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

在另一个地方:

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

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

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

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

C
Community

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

如果您想要 .NET 能够提供的最快速度,请选择此答案。

如果您想要一种非常非常简单的克隆方法,请忽略此答案。

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

比其他方法快 10 倍

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

比涉及序列化/反序列化的任何东西快 10 倍;

非常接近 .NET 能够达到的理论最大速度。

而且方法...

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

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

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

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

值类型与引用类型

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

如果你有一个“结构”,它是一个值类型,所以你可以复制它,内容将被克隆(但除非你使用本文中的技术,否则它只会进行浅层克隆)。

如果你有一个“类”,它是一个引用类型,所以如果你复制它,你所做的就是复制指向它的指针。要创建真正的克隆,您必须更具创造性,并利用值类型和引用类型之间的差异,从而在内存中创建原始对象的另一个副本。

请参阅differences between value types and references types

有助于调试的校验和

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

我发现这种方法非常有用:通常,您只想克隆对象的一部分,而不是整个对象。

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

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

我们可以让一个(或多个)线程修改他们拥有的类,然后将这个类的完整副本推送到 ConcurrentQueue 中。

然后我们有一个(或多个)线程将这些类的副本拉出并处理它们。

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

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

更新

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


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

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

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

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

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

表达式树

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

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

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

用法

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

var clone = ObjectCloner.DeepClone(original);

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

var clone = original.DeepClone();

表现

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


序列化在 2019 年仍然流行的原因是代码生成只能在受信任的环境中工作。这意味着它不能在 Unity 或 iOS 中工作,而且可能永远也不会。所以代码生成是不可移植的。
我使用了 NewtonSoft 的 12.0.3 版本,我的类没有参数构造函数,它对我有用
不错的包,今天开始用了。我注意到一件事,命名空间和类名是相同的,所以要使用类 ObjectCloner 的静态方法,尽管使用了指令,但我必须显式地来自命名空间,例如 - ObjectCloner.ObjectCloner.DeepClone(someObject)
x
xr280xr

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


d
dougajmcdonald

这是一个深拷贝实现:

public static object CloneObject(object opSource)
{
    //grab the type and create a new instance of that type
    Type opSourceType = opSource.GetType();
    object opTarget = CreateInstanceOfType(opSourceType);

    //grab the properties
    PropertyInfo[] opPropertyInfo = opSourceType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    //iterate over the properties and if it has a 'set' method assign it from the source TO the target
    foreach (PropertyInfo item in opPropertyInfo)
    {
        if (item.CanWrite)
        {
            //value types can simply be 'set'
            if (item.PropertyType.IsValueType || item.PropertyType.IsEnum || item.PropertyType.Equals(typeof(System.String)))
            {
                item.SetValue(opTarget, item.GetValue(opSource, null), null);
            }
            //object/complex types need to recursively call this method until the end of the tree is reached
            else
            {
                object opPropertyValue = item.GetValue(opSource, null);
                if (opPropertyValue == null)
                {
                    item.SetValue(opTarget, null, null);
                }
                else
                {
                    item.SetValue(opTarget, CloneObject(opPropertyValue), null);
                }
            }
        }
    }
    //return the new item
    return opTarget;
}

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

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

https://github.com/kalisohn/CloneBehave

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

例如:下面的代码将 deepClone 地址,但只执行 _currentJob 字段的浅拷贝。

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

  public string Name { get; set; }

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

  public Person Manager { get; set; }
}

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

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

Address adrClone = adr.Clone();

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

S
Sean McAvoy

创建一个扩展:

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

并这样称呼它:

NewObject = OldObject.Clone();

T
Toxantron

代码生成器

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

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


L
LuckyLikey

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

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


G
GorvGoyl

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

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


D
Daniele D.

这是一个快速而简单的解决方案,它对我有用,而不需要序列化/反序列化。

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

M
Mifeet

按着这些次序:

定义一个具有返回 T 的只读 Self 属性的 ISelf,以及派生自 ISelf 并包含方法 T Clone() 的 ICloneable

然后定义一个 CloneBase 类型,该类型实现受保护的虚拟泛型 VirtualClone 将 MemberwiseClone 转换为传入的类型。

每个派生类型都应该通过调用基本克隆方法来实现 VirtualClone,然后做任何需要做的事情来正确克隆父 VirtualClone 方法尚未处理的派生类型的那些方面。

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


M
Michael Brown

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

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

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


使用 AnyClone,我刚刚安装了名为 .Clone() 的 NuGet 包,它在这里的 Blazor 项目中运行良好!
J
Jeroen Ritmeijer

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

} 

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅