我想做一些事情,比如:

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

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

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

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


当前回答

下面是一个深度拷贝实现:

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

其他回答

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

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

在大多数实际情况下,您还希望对复制过程进行尽可能多的粒度控制,因为您不仅耦合到数据访问框架,而且在实践中,复制的业务对象几乎不应该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
            };
        }
    }

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

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

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

https://github.com/AlenToma/FastDeepCloner

这将把一个对象的所有可读写财产复制到另一个对象。

 public class PropertyCopy<TSource, TTarget> 
                        where TSource: class, new()
                        where TTarget: class, new()
        {
            public static TTarget Copy(TSource src, TTarget trg, params string[] properties)
            {
                if (src==null) return trg;
                if (trg == null) trg = new TTarget();
                var fulllist = src.GetType().GetProperties().Where(c => c.CanWrite && c.CanRead).ToList();
                if (properties != null && properties.Count() > 0)
                    fulllist = fulllist.Where(c => properties.Contains(c.Name)).ToList();
                if (fulllist == null || fulllist.Count() == 0) return trg;

                fulllist.ForEach(c =>
                    {
                        c.SetValue(trg, c.GetValue(src));
                    });

                return trg;
            }
        }

这是你使用它的方式:

 var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave,
                                                            "Creation",
                                                            "Description",
                                                            "IdTicketStatus",
                                                            "IdUserCreated",
                                                            "IdUserInCharge",
                                                            "IdUserRequested",
                                                            "IsUniqueTicketGenerated",
                                                            "LastEdit",
                                                            "Subject",
                                                            "UniqeTicketRequestId",
                                                            "Visibility");

或复制所有内容:

var cloned = Utils.PropertyCopy<TKTicket, TKTicket>.Copy(_tmp, dbsave);