Есть ли лучший способ создать глубокие и мелкие клоны в С#?
Я создаю объект для проекта, и есть некоторые экземпляры, которые я должен создать для этих объектов глубокую копию. Я придумал использование встроенной функции для С#, которая является элементом MemberwiseClone(). Проблема, которая меня беспокоит, всякий раз, когда появляется новый класс, который я создал, мне пришлось бы написать функцию, такую как код ниже, для мелкой копии. Может кто-то, пожалуйста, помогите мне улучшить эту часть и дать мне мелкую копию, которая лучше чем вторая строка кода. спасибо:)
ДАЛЬНЕЙШАЯ КОПИЯ:
public static RoomType CreateTwin(RoomType roomType)
{
return (roomType.MemberwiseClone() as RoomType);
}
DEEP COPY:
public static T CreateDeepClone<T>(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);
}
}
Ответы
Ответ 1
MemberwiseClone не является хорошим выбором для Deep Copy (MSDN):
Метод MemberwiseClone создает мелкую копию, создавая новую объект, а затем копирование нестатических полей текущего объекта в новый объект. Если поле является типом значения, побитовая копия поле выполняется. Если поле является ссылочным типом, ссылка скопировано, но упомянутый объект не; поэтому оригинал объект и его клон относятся к одному и тому же объекту.
Это означает, что если клонированный объект имеет общедоступные поля или свойства ссылочного типа, они будут ссылаться на ту же ячейку памяти, что и исходные поля/свойства объекта, поэтому каждое изменение клонированного объекта будет отражено в исходном объекте. Это не настоящая глубокая копия.
Вы можете использовать BinarySerialization для создания полностью независимого экземпляра объекта, см. страницу MSDN
Тесты NUnit:
public class MemoryUtilsFixture
{
[Test]
public void DeepCopyThrowWhenCopyInstanceOfNonSerializableType()
{
var nonSerializableInstance = new CustomNonSerializableType();
Assert.Throws<SerializationException>(() => nonSerializableInstance.DeepCopy(true));
}
[Test]
public void DeepCopyThrowWhenPassedInNull()
{
object instance = null;
Assert.Throws<ArgumentNullException>(() => 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
{
}
}
Ответ 2
Вы также можете использовать отражение для создания копии объекта, это должен быть самый быстрый способ, потому что сериализация также использует отражение.
Вот какой код (протестирован):
public static T DeepClone<T>(this T original, params Object[] args)
{
return original.DeepClone(new Dictionary<Object, Object>(), args);
}
private static T DeepClone<T>(this T original, Dictionary<Object, Object> 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<T>() 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 ( e.g. 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;
}
Update
Добавлен еще один код, теперь вы можете использовать этот метод для копирования сложных объектов (даже массивов с несколькими измерениями). Обратите внимание, что делегаты по-прежнему не реализованы.
Если вам нужна полная реализация, вам нужно обработать интерфейс ISerializable
, который не очень тяжелый, но требуется некоторое время, чтобы отразить существующий код. Сделал это один раз для удаленной реализации.
Ответ 3
Решение, использующее сериализацию, как было предложено sll, является самым простым, но не работает, если тип, который вы пытаетесь клонировать, не является сериализуемым.
Код Felix K. - хорошая альтернатива, но я нашел несколько проблем с ним. Вот пересмотренная версия, которая исправляет некоторые из проблем, которые я нашел. Я также удалил некоторые функции, которые мне не нужны (например, параметры конструктора).
/// <summary>
/// A DeepClone method for types that are not serializable.
/// </summary>
public static T DeepCloneWithoutSerialization<T>(this T original)
{
return original.deepClone(new Dictionary<object, object>());
}
static T deepClone<T>(this T original, Dictionary<object, object> copies)
{
return (T)original.deepClone(typeof(T), copies);
}
/// <summary>
/// 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.
/// </summary>
static object deepClone(this object original, Type t, Dictionary<object, object> 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;
}