¿Hay una manera mucho mejor de crear clones profundos y poco profundos en C #?

He estado creando un objeto para un proyecto y hay algunas instancias en las que tengo que crear una copia profunda para estos objetos que he encontrado con el uso de una función integrada para C # que es MemberwiseClone (). El problema que me preocupa es cada vez que hay una clase nueva que creé, tendría que escribir una función como el siguiente para una copia superficial … ¿Puede alguien ayudarme a mejorar esta parte y darme una copia superficial que sea mejor? que la segunda línea de código. Gracias 🙂

COPIA SUPERFICIAL:

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

COPIA PROFUNDA:

 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 no es una buena opción para hacer una copia profunda ( MSDN ):

El método MemberwiseClone crea una copia superficial creando un nuevo objeto y luego copiando los campos no estáticos del objeto actual al nuevo objeto. Si un campo es un tipo de valor, se realiza una copia bit por bit del campo. Si un campo es un tipo de referencia, la referencia se copia pero el objeto referido no ; por lo tanto, el objeto original y su clon se refieren al mismo objeto.

Esto significa que si el objeto clonado tiene propiedades o campos públicos de tipo de referencia, se referirían a la misma ubicación de memoria que los campos / propiedades del objeto original, de modo que cada cambio en el objeto clonado se reflejará en el objeto inicial. Esta no es una verdadera copia profunda.

Puede utilizar BinarySerialization para crear una instancia completamente independiente del objeto, consulte la página MSDN de la clase BinaryFormatter para obtener un ejemplo de serialización.


Ejemplo y arnés de prueba:

Método de extensión para crear una copia profunda de un objeto dado:

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

Pruebas 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 { } } 

También puede usar el reflection para crear una copia del objeto, esta debería ser la forma más rápida, ya que la serialización también usa el reflection.

Aquí un código (probado):

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

Actualizar

Se agregó más código, ahora puede usar el método para copiar objetos complejos (incluso matrices con múltiples dimensiones). Tenga en cuenta que los delegates aún no están implementados.

Si desea una implementación completa, debe manejar la interfaz ISerializable , que no es realmente difícil, pero requiere cierto tiempo para reflejar el código existente. Lo hice una vez para una implementación remota.

La solución que utiliza serialización, como lo sugiere sll, es por lejos la más simple, pero no funciona si el tipo que intenta clonar no es serializable.

El código de Felix K. es una buena alternativa, pero encontré algunos problemas. Aquí hay una versión revisada que corrige algunos de los problemas que encontré. También eliminé parte de la funcionalidad que no necesitaba (por ejemplo, parámetros de constructor).

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