我想做一些事情,比如:

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

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

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

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


当前回答

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

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

其他回答

在我使用的代码库中,我们有一个来自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原谅运算符)

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

} 

代码生成器

我们已经看到了很多想法,从序列化到手动实现再到反射,我想使用CGbR代码生成器提出一种完全不同的方法。生成克隆方法具有内存和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;
    }
}

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

对于克隆过程,可以先将对象转换为字节数组,然后再转换回对象。

public static class Extentions
{
    public static T Clone<T>(this T obj)
    {
        byte[] buffer = BinarySerialize(obj);
        return (T)BinaryDeserialize(buffer);
    }

    public static byte[] BinarySerialize(object obj)
    {
        using (var stream = new MemoryStream())
        {
            var formatter = new BinaryFormatter(); 
            formatter.Serialize(stream, obj); 
            return stream.ToArray();
        }
    }

    public static object BinaryDeserialize(byte[] buffer)
    {
        using (var stream = new MemoryStream(buffer))
        {
           var formatter = new BinaryFormatter(); 
           return formatter.Deserialize(stream);
        }
    }
}

必须为序列化进程序列化对象。

[Serializable]
public class MyObject
{
    public string Name  { get; set; }
}

用法:

MyObject myObj  = GetMyObj();
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"

我希望这有助于:)