我想做一些事情,比如:

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

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

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

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


当前回答

对@Konrad和@craastad的添加,使用内置的System.Text.Json for.NET>5

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?pivots=dotnet-5-0

方法:

public static T Clone<T>(T source)
{
    var serialized = JsonSerializer.Serialize(source);
    return JsonSerializer.Deserialize<T>(serialized);
}

扩展方法:

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonSerializer.Serialize(source);
        return JsonSerializer.Deserialize<T>(serialized);
    }
}

其他回答

当使用Marc Gravells protobuf net作为序列化程序时,接受的答案需要一些轻微的修改,因为要复制的对象不会被归因于[Serializable],因此不可序列化,Clone方法将抛出异常。我修改了它以与protobuf net一起使用:

public static T Clone<T>(this T source)
{
    if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if(Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

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

这将检查[ProtoControl]属性的存在,并使用protobufs自己的格式化程序来序列化对象。

虽然一种方法是实现ICloneable接口(在这里描述,所以我不会反悔),但这里有一个很好的深度克隆对象复制器,我不久前在代码项目中找到了它,并将其合并到我们的代码中。如其他地方所述,它要求您的对象是可序列化的。

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的新扩展方法,请将该方法更改为具有以下签名:

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

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

EDIT(2015年1月10日)我想我会重新考虑这个问题,要说我最近开始使用(Newtonsoft)Json来做这个,它应该更轻,并避免[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);
}

映射器执行深度复制。为对象的每个成员创建一个新对象并分配其所有值。它递归地处理每个非基元内部成员。

我建议您选择目前发展最快、最活跃的产品之一。我建议使用UltraMapperhttps://github.com/maurosampietro/UltraMapper

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

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

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