有没有更好的方法在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 (this T original, Dictionary () 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 (this T original, Dictionary