有没有更好的方法在C#中创建深度和浅层克隆?

我一直在为项目创建对象,有些实例我必须为这些对象创建一个深层副本我已经想到了使用C#的内置函数,即MemberwiseClone()。 困扰我的问题是每当我创建一个新类时,我都必须编写一个类似下面的代码的函数来进行浅拷贝。有人请帮助我改进这部分并给我一个更好的浅拷贝比第二行代码。 谢谢 :)

SHALLOW COPY:

public static RoomType CreateTwin(RoomType roomType) { return (roomType.MemberwiseClone() as RoomType); } 

深度复制:

 public static T CreateDeepClone(T source) { if (!typeof(T).IsSerializable) { throw new ArgumentException("The type must be serializable.", "source"); } 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); } } 

MemberwiseClone不是做Deep Copy( MSDN )的好选择:

MemberwiseClone方法通过创建新对象,然后将当前对象的非静态字段复制到新对象来创建浅表副本。 如果字段是值类型,则执行字段的逐位复制。 如果字段是引用类型,则复制引用但引用的对象不是 ; 因此,原始对象及其克隆引用相同的对象。

这意味着如果克隆对象具有引用类型公共字段或属性,则它们将与原始对象的字段/属性一起提供到相同的内存位置,因此克隆对象中的每个更改都将反映在初始对象中。 这不是真正的深层复制品。

您可以使用BinarySerialization创建对象的完全独立的实例,请参阅BinaryFormatter类的 MSDN页面以获取序列化示例。


示例和测试工具:

用于创建给定对象的深层副本的扩展方法:

 public static class MemoryUtils { ///  /// Creates a deep copy of a given object instance ///  /// Type of a given object /// Object to be cloned ///  /// A value which indicating whether exception should be thrown in case of /// error whils clonin /// Returns a deep copy of a given object /// Uses BInarySerialization to create a true deep copy public static TObject DeepCopy(this TObject instance, bool throwInCaseOfError) where TObject : class { if (instance == null) { throw new ArgumentNullException("instance"); } TObject clonedInstance = default(TObject); try { using (var stream = new MemoryStream()) { BinaryFormatter binaryFormatter = new BinaryFormatter(); binaryFormatter.Serialize(stream, instance); // reset position to the beginning of the stream so // deserialize would be able to deserialize an object instance stream.Position = 0; clonedInstance = (TObject)binaryFormatter.Deserialize(stream); } } catch (Exception exception) { string errorMessage = String.Format(CultureInfo.CurrentCulture, "Exception Type: {0}, Message: {1}{2}", exception.GetType(), exception.Message, exception.InnerException == null ? String.Empty : String.Format(CultureInfo.CurrentCulture, " InnerException Type: {0}, Message: {1}", exception.InnerException.GetType(), exception.InnerException.Message)); Debug.WriteLine(errorMessage); if (throwInCaseOfError) { throw; } } return clonedInstance; } } 

NUnit测试:

 public class MemoryUtilsFixture { [Test] public void DeepCopyThrowWhenCopyInstanceOfNonSerializableType() { var nonSerializableInstance = new CustomNonSerializableType(); Assert.Throws(() => nonSerializableInstance.DeepCopy(true)); } [Test] public void DeepCopyThrowWhenPassedInNull() { object instance = null; Assert.Throws(() => instance.DeepCopy(true)); } [Test] public void DeepCopyThrowWhenCopyInstanceOfNonSerializableTypeAndErrorsDisabled() { var nonSerializableInstance = new CustomNonSerializableType(); object result = null; Assert.DoesNotThrow(() => result = nonSerializableInstance.DeepCopy(false)); Assert.IsNull(result); } [Test] public void DeepCopyShouldCreateExactAndIndependentCopyOfAnObject() { var instance = new CustomSerializableType { DateTimeValueType = DateTime.Now.AddDays(1).AddMilliseconds(123).AddTicks(123), NumericValueType = 777, StringValueType = Guid.NewGuid().ToString(), ReferenceType = new CustomSerializableType { DateTimeValueType = DateTime.Now, StringValueType = Guid.NewGuid().ToString() } }; var deepCopy = instance.DeepCopy(true); Assert.IsNotNull(deepCopy); Assert.IsFalse(ReferenceEquals(instance, deepCopy)); Assert.That(instance.NumericValueType == deepCopy.NumericValueType); Assert.That(instance.DateTimeValueType == deepCopy.DateTimeValueType); Assert.That(instance.StringValueType == deepCopy.StringValueType); Assert.IsNotNull(deepCopy.ReferenceType); Assert.IsFalse(ReferenceEquals(instance.ReferenceType, deepCopy.ReferenceType)); Assert.That(instance.ReferenceType.DateTimeValueType == deepCopy.ReferenceType.DateTimeValueType); Assert.That(instance.ReferenceType.StringValueType == deepCopy.ReferenceType.StringValueType); } [Serializable] internal sealed class CustomSerializableType { public int NumericValueType { get; set; } public string StringValueType { get; set; } public DateTime DateTimeValueType { get; set; } public CustomSerializableType ReferenceType { get; set; } } public sealed class CustomNonSerializableType { } } 

您也可以使用reflection来创建对象的副本,这应该是最快的方法,因为序列化也使用reflection。

这里有一些代码(测试过):

 public static T DeepClone(this T original, params Object[] args) { return original.DeepClone(new Dictionary(), args); } private static T DeepClone(this T original, Dictionary copies, params Object[] args) { T result; Type t = original.GetType(); Object tmpResult; // Check if the object already has been copied if (copies.TryGetValue(original, out tmpResult)) { return (T)tmpResult; } else { if (!t.IsArray) { /* Create new instance, at this point you pass parameters to * the constructor if the constructor if there is no default constructor * or you change it to Activator.CreateInstance() if there is always * a default constructor */ result = (T)Activator.CreateInstance(t, args); copies.Add(original, result); // Maybe you need here some more BindingFlags foreach (FieldInfo field in t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance)) { /* You can filter the fields here ( look for attributes and avoid * unwanted fields ) */ Object fieldValue = field.GetValue(original); // Check here if the instance should be cloned Type ft = field.FieldType; /* You can check here for ft.GetCustomAttributes(typeof(SerializableAttribute), false).Length != 0 to * avoid types which do not support serialization ( eg NetworkStreams ) */ if (fieldValue != null && !ft.IsValueType && ft != typeof(String)) { fieldValue = fieldValue.DeepClone(copies); /* Does not support parameters for subobjects nativly, but you can provide them when using * a delegate to create the objects instead of the Activator. Delegates should not work here * they need some more love */ } field.SetValue(result, fieldValue); } } else { // Handle arrays here Array originalArray = (Array)(Object)original; Array resultArray = (Array)originalArray.Clone(); copies.Add(original, resultArray); // If the type is not a value type we need to copy each of the elements if (!t.GetElementType().IsValueType) { Int32[] lengths = new Int32[t.GetArrayRank()]; Int32[] indicies = new Int32[lengths.Length]; // Get lengths from original array for (int i = 0; i < lengths.Length; i++) { lengths[i] = resultArray.GetLength(i); } Int32 p = lengths.Length - 1; /* Now we need to iterate though each of the ranks * we need to keep it generic to support all array ranks */ while (Increment(indicies, lengths, p)) { Object value = resultArray.GetValue(indicies); if (value != null) resultArray.SetValue(value.DeepClone(copies), indicies); } } result = (T)(Object)resultArray; } return result; } } private static Boolean Increment(Int32[] indicies, Int32[] lengths, Int32 p) { if (p > -1) { indicies[p]++; if (indicies[p] < lengths[p]) { return true; } else { if (Increment(indicies, lengths, p - 1)) { indicies[p] = 0; return true; } else { return false; } } } return false; } 

更新

添加了一些代码,现在您可以使用该方法复制复杂对象(甚至是具有多个维度的数组)。 请注意,代表仍未实现。

如果你想要一个完整的实现,你需要处理ISerializable接口,这不是很难,但需要一些时间来反映现有的代码。 这是一次用于远程实现吗?

正如sll所建议的那样,使用序列化的解决方案是最简单的,但如果您尝试克隆的类型不可序列化则不起作用。

来自Felix K.的代码是一个很好的选择,但我发现它有一些问题。 这是一个修订版本,修复了我发现的一些问题。 我还删除了一些我不需要的function(例如构造函数参数)。

 ///  /// A DeepClone method for types that are not serializable. ///  public static T DeepCloneWithoutSerialization(this T original) { return original.deepClone(new Dictionary()); } static T deepClone(this T original, Dictionary copies) { return (T)original.deepClone(typeof(T), copies); } ///  /// Deep clone an object without using serialisation. /// Creates a copy of each field of the object (and recurses) so that we end up with /// a copy that doesn't include any reference to the original object. ///  static object deepClone(this object original, Type t, Dictionary copies) { // Check if object is immutable or copy on update if (t.IsValueType || original == null || t == typeof(string) || t == typeof(Guid)) return original; // Interfaces aren't much use to us if (t.IsInterface) t = original.GetType(); object tmpResult; // Check if the object already has been copied if (copies.TryGetValue(original, out tmpResult)) return tmpResult; object result; if (!t.IsArray) { result = Activator.CreateInstance(t); copies.Add(original, result); // Maybe you need here some more BindingFlags foreach (var field in t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance)) { var fieldValue = field.GetValue(original); field.SetValue(result, fieldValue.deepClone(field.FieldType, copies)); } } else { // Handle arrays here var originalArray = (Array)original; var resultArray = (Array)originalArray.Clone(); copies.Add(original, resultArray); var elementType = t.GetElementType(); // If the type is not a value type we need to copy each of the elements if (!elementType.IsValueType) { var lengths = new int[t.GetArrayRank()]; var indicies = new int[lengths.Length]; // Get lengths from original array for (var i = 0; i < lengths.Length; i++) lengths[i] = resultArray.GetLength(i); var p = lengths.Length - 1; /* Now we need to iterate though each of the ranks * we need to keep it generic to support all array ranks */ while (increment(indicies, lengths, p)) { var value = resultArray.GetValue(indicies); if (value != null) resultArray.SetValue(value.deepClone(elementType, copies), indicies); } } result = resultArray; } return result; } static bool increment(int[] indicies, int[] lengths, int p) { if (p > -1) { indicies[p]++; if (indicies[p] < lengths[p]) return true; if (increment(indicies, lengths, p - 1)) { indicies[p] = 0; return true; } } return false; }