我想做一些事情,比如:

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

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

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

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


当前回答

除了这里的一些精彩答案外,您可以在C#9.0及更高版本中完成以下操作(假设您可以将类转换为记录):

record Record
{
    public int Property1 { get; set; }

    public string Property2 { get; set; }
}

然后,只需使用with运算符将一个对象的值复制到新对象。

var object1 = new Record()
{
    Property1 = 1,
    Property2 = "2"
};

var object2 = object1 with { };
// object2 now has Property1 = 1 & Property2 = "2"

我希望这有助于:)

其他回答

找到了这个包,与之相比,DeepCloner似乎更快,而且没有依赖关系。

https://github.com/AlenToma/FastDeepCloner

如果您已经在使用第三方应用程序(如ValueInjector或Automapper),可以执行以下操作:

MyObject oldObj; // The existing object to clone

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

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

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

通用方法在技术上都是有效的,但我只想补充一点,因为我们实际上很少需要真正的深度复制,我强烈反对在实际的业务应用程序中使用通用深度复制,因为这使得您可能会在许多地方复制对象,然后显式修改,这很容易丢失。

在大多数实际情况下,您还希望对复制过程进行尽可能多的粒度控制,因为您不仅耦合到数据访问框架,而且在实践中,复制的业务对象几乎不应该100%相同。举一个ORM用来识别对象引用的referenceId的例子,一个完整的深度副本也会复制这个id,所以在内存中,对象会不同,一旦你将其提交到数据存储,它就会抱怨,因此,无论如何,您都必须在复制后手动修改此财产,如果对象发生更改,则需要在使用通用深度复制的所有位置对其进行调整。

用ICloneable扩展@cregox答案,到底什么是深度副本?它只是堆上新分配的一个对象,与原始对象相同,但占用不同的内存空间,而不是使用通用的克隆器功能,为什么不创建一个新对象呢?

我个人在我的域对象上使用静态工厂方法的思想。

例子:

    public class Client
    {
        public string Name { get; set; }

        protected Client()
        {
        }

        public static Client Clone(Client copiedClient)
        {
            return new Client
            {
                Name = copiedClient.Name
            };
        }
    }

    public class Shop
    {
        public string Name { get; set; }

        public string Address { get; set; }

        public ICollection<Client> Clients { get; set; }

        public static Shop Clone(Shop copiedShop, string newAddress, ICollection<Client> clients)
        {
            var copiedClients = new List<Client>();
            foreach (var client in copiedShop.Clients)
            {
                copiedClients.Add(Client.Clone(client));
            }

            return new Shop
            {
                Name = copiedShop.Name,
                Address = newAddress,
                Clients = copiedClients
            };
        }
    }

如果有人在研究如何在保持对复制过程的完全控制的同时构建对象实例化,这是我个人非常成功的解决方案。受保护的构造函数也使其成为可能,其他开发人员被迫使用工厂方法,该方法提供了一个简洁的单点对象实例化,封装了对象内部的构造逻辑。如果需要,您还可以重载该方法,并为不同的位置提供多个克隆逻辑。

最短的方式,但需要依赖:

using Newtonsoft.Json;
    public static T Clone<T>(T source) =>
        JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));

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

}