我想做一些事情,比如:

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

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

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

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


当前回答

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

其他回答

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

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

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

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

如果您还需要实现浅层复制方法,则此解决方案也很容易扩展。

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

public T ShallowCopy()
{
    return (T)this.MemberwiseClone();
}

在我使用的代码库中,我们有一个来自GitHub项目Burtsev Alexey/net对象深度副本的ObjectExtension.cs文件副本。它已经9岁了。虽然我们后来意识到,对于更大的对象结构来说,它是非常慢的,但它还是有效的。

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

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

用于快速对象克隆的C#扩展方法。这是Alexey Burtsev深度复印机的速度优化叉。根据您的使用情况,这将比原始版本快2倍-3倍。它还修复了原始代码中存在的一些错误。与经典的二进制序列化/反序列化深度克隆技术相比,此版本的速度大约快七倍(对象包含的数组越多,加速因子越大)。通过以下技术实现加速:缓存对象反射结果不要深度复制原语或不可变结构&类(例如枚举和字符串)为了提高引用的局部性,处理内部循环中的“快速”维度或多维数组使用编译的lambda表达式调用MemberwiseClone如何使用:使用Baksteen.Extensions.DeepCopy;...var myobject=新建SomeClass();...var myclone=myobject.DepCopy()!;//创建原始对象的新深度副本注意:只有在项目中启用了可为null的引用类型时,才需要感叹号(null原谅运算符)

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

在最初的答案中,如果调用者在基类对象上调用DeepCopy,则克隆的对象属于基类。但下面的代码将返回派生类。

using Newtonsoft.Json;

public static T DeepCopy<T>(this T source)
{
    return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), source.GetType());
}

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

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

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

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

表达式树

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

我自己遇到了这个问题,但没有找到令人满意的解决方案,因此我决定创建一个包,它可以做到这一点,并且可以处理所有类型的代码,而且速度几乎与自定义编写的代码一样快。

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

用法

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

var clone = ObjectCloner.DeepClone(original);

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

var clone = original.DeepClone();

表演

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

我知道,这个问题和答案在这里停留了一段时间,下面不是很好的答案,而是观察,最近我在检查是否确实没有克隆隐私(如果没有,我就不会是我自己了;)时,我很高兴地复制了粘贴的@johnc更新的答案。

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

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

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

并天真地放弃了这样的课程(事实上,有更多这样的课程,但它们是无关的):

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

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

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

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

代码如下:

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

结果是:

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

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

要旨

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