在c#中是否有更好的方法来创建深克隆和浅克隆?



我一直在为一个项目创建对象,并且有一些实例,我必须为这个对象创建一个深度副本,我已经提出使用c#内置函数MemberwiseClone()。困扰我的问题是,每当有一个新的类,我创建,我将不得不写一个函数像下面的代码浅拷贝..有人可以帮助我改进这部分,给我一个浅拷贝,比第二行代码更好。谢谢:)

浅拷贝:

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

深拷贝:

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

MemberwiseClone不是做深度拷贝(MSDN)的好选择:

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

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

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


示例和测试线束:

创建给定对象的深度拷贝的扩展方法:

public static class MemoryUtils
{
    /// <summary>
    /// Creates a deep copy of a given object instance
    /// </summary>
    /// <typeparam name="TObject">Type of a given object</typeparam>
    /// <param name="instance">Object to be cloned</param>
    /// <param name="throwInCaseOfError">
    /// A value which indicating whether exception should be thrown in case of
    /// error whils clonin</param>
    /// <returns>Returns a deep copy of a given object</returns>
    /// <remarks>Uses BInarySerialization to create a true deep copy</remarks>
    public static TObject DeepCopy<TObject>(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<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
    {            
    }
}

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

下面是一些代码(已测试):
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;
                if (p > -1) indicies[indicies.Length - 1] = -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的代码是一个很好的选择,但我发现它有一些问题。以下是修改后的版本,修复了我发现的一些问题。我还删除了一些我不需要的功能(例如。构造函数参数)。

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

最新更新