当前位置:  开发笔记 > 编程语言 > 正文

如何在.NET中执行对象的深层复制(特别是C#)?

如何解决《如何在.NET中执行对象的深层复制(特别是C#)?》经验,为你挑选了6个好方法。

我想要一个真正的深拷贝.在Java中,这很容易,但是你如何在C#中做到这一点?



1> Kilhoffer..:

我已经看到了一些不同的方法,但我使用通用的实用方法:

public static T DeepClone(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

笔记:

您的班级必须标记为[Serializable]为了使其工作.

您的源文件必须包含以下代码:

using System.Runtime.Serialization.Formatters.Binary;
using System.IO;


递归的MemberwiseClone也会做深度复制,它比BinaryFormatter快3倍,不需要默认构造函数或任何属性.请参阅我的回答:http://stackoverflow.com/a/11308879/235715
事件订阅包含在序列化图中,因为BinaryFormatter通过反射使用字段,事件只是委托类型的字段加上添加/删除/调用方法.您可以在事件中使用[field:NonSerialized]来避免这种情况.
如果对象有事件会发生什么,是否由于序列化而丢失了所有内容?
@ Sean87:在类声明之上,添加``Serializable]`.所以`[Serializable]公共类Foo {}`会将`Foo`标记为可序列化.

2> Alex Burtsev..:

我写了一个深度对象复制扩展方法,基于递归"MemberwiseClone".速度快(比BinaryFormatter 三倍),适用于任何对象.您不需要默认构造函数或可序列化属性.

源代码:

using System.Collections.Generic;
using System.Reflection;
using System.ArrayExtensions;

namespace System
{
    public static class ObjectExtensions
    {
        private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);

        public static bool IsPrimitive(this Type type)
        {
            if (type == typeof(String)) return true;
            return (type.IsValueType & type.IsPrimitive);
        }

        public static Object Copy(this Object originalObject)
        {
            return InternalCopy(originalObject, new Dictionary(new ReferenceEqualityComparer()));
        }
        private static Object InternalCopy(Object originalObject, IDictionary visited)
        {
            if (originalObject == null) return null;
            var typeToReflect = originalObject.GetType();
            if (IsPrimitive(typeToReflect)) return originalObject;
            if (visited.ContainsKey(originalObject)) return visited[originalObject];
            if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
            var cloneObject = CloneMethod.Invoke(originalObject, null);
            if (typeToReflect.IsArray)
            {
                var arrayType = typeToReflect.GetElementType();
                if (IsPrimitive(arrayType) == false)
                {
                    Array clonedArray = (Array)cloneObject;
                    clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
                }

            }
            visited.Add(originalObject, cloneObject);
            CopyFields(originalObject, visited, cloneObject, typeToReflect);
            RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
            return cloneObject;
        }

        private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary visited, object cloneObject, Type typeToReflect)
        {
            if (typeToReflect.BaseType != null)
            {
                RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
                CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
            }
        }

        private static void CopyFields(object originalObject, IDictionary visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func filter = null)
        {
            foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
            {
                if (filter != null && filter(fieldInfo) == false) continue;
                if (IsPrimitive(fieldInfo.FieldType)) continue;
                var originalFieldValue = fieldInfo.GetValue(originalObject);
                var clonedFieldValue = InternalCopy(originalFieldValue, visited);
                fieldInfo.SetValue(cloneObject, clonedFieldValue);
            }
        }
        public static T Copy(this T original)
        {
            return (T)Copy((Object)original);
        }
    }

    public class ReferenceEqualityComparer : EqualityComparer
    {
        public override bool Equals(object x, object y)
        {
            return ReferenceEquals(x, y);
        }
        public override int GetHashCode(object obj)
        {
            if (obj == null) return 0;
            return obj.GetHashCode();
        }
    }

    namespace ArrayExtensions
    {
        public static class ArrayExtensions
        {
            public static void ForEach(this Array array, Action action)
            {
                if (array.LongLength == 0) return;
                ArrayTraverse walker = new ArrayTraverse(array);
                do action(array, walker.Position);
                while (walker.Step());
            }
        }

        internal class ArrayTraverse
        {
            public int[] Position;
            private int[] maxLengths;

            public ArrayTraverse(Array array)
            {
                maxLengths = new int[array.Rank];
                for (int i = 0; i < array.Rank; ++i)
                {
                    maxLengths[i] = array.GetLength(i) - 1;
                }
                Position = new int[array.Rank];
            }

            public bool Step()
            {
                for (int i = 0; i < Position.Length; ++i)
                {
                    if (Position[i] < maxLengths[i])
                    {
                        Position[i]++;
                        for (int j = 0; j < i; j++)
                        {
                            Position[j] = 0;
                        }
                        return true;
                    }
                }
                return false;
            }
        }
    }

}


谢谢亚历克斯,是的,我需要打电话给副本而且有效!
@MattSmith你是绝对正确的GetHashCode,检查它,是的StackOverflow异常 - https://gist.github.com/Burtsev-Alexey/11227277
@MattSmith它适用于代表,但我专心禁用它(通过设置null),请参阅https://github.com/Burtsev-Alexey/net-object-deep-copy/issues/7,订阅者被克隆,最后,如果你有两个对象A和B连接(通过事件订阅)你会得到对象A'和B'连接,这是正确的,但这不是大多数人想要的克隆对象.
@MattSmith字符串被视为基元,因为1:它们是不可变的,2:在字符串上调用受保护的MemberwiseClone将导致内存损坏,字符串数据将变为随机字符,很快.NET运行时将崩溃并出现内部错误,称其为a但是在.NET中:-)
@ Alex141 - 刚遇到同样的困惑.所有相关代码都在引用文件中,下面是一个ArrayExtensions命名空间.
这是一个非常聪明和强大的实现,但是在决定是否适合您的数据模型之前,您必须考虑一些事项。“ Memberwiseclone()”之所以如此之快,是因为它不调用构造函数。因此,如果您的构造函数正在进行繁重的工作(例如事件订阅),那么您就不走运了。它依赖于复制对象的私有字段,从而绕过属性和方法中的业务逻辑。例如,即使所有实例都已更改,我也看到将“ hashCode”字段复制到“ HashSet”集合中。

3> Neil..:

以Kilhoffer的解决方案为基础......

使用C#3.0,您可以创建一个扩展方法,如下所示:

public static class ExtensionMethods
{
    // Deep clone
    public static T DeepClone(this T a)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, a);
            stream.Position = 0;
            return (T) formatter.Deserialize(stream);
        }
    }
}

它扩展了使用DeepClone方法标记为[Serializable]的任何类

MyClass copy = obj.DeepClone();


为此添加"public static T DeepClone (this T a)其中T:ISerializable"
@Amir - 类没有必要实现ISerializable,使用SerializableAttribute进行标记就足够了.该属性使用反射来执行序列化,而界面允许您编写自定义序列化程序
我同意你的陈述,但我喜欢Amir的建议b/c它提供编译时检查.有没有办法调和这两个?
传递单元测试var stringbuilder = new StringBuilder("TestData"); var copy = stringbuilder.DeepClone(); Assert.IsFalse(等于(stringbuilder的,复制)); 非常感谢.
@Neil此方法比NestedMemberwiseClone方法慢10倍,请参阅本页的帖子.

4> Contango..:

您可以使用嵌套的MemberwiseClone进行深层复制.它与复制值结构的速度几乎相同,并且比(a)反射或(b)序列化(如本页其他答案中所述)快一个数量级.

请注意,如果您使用嵌套的MemberwiseClone进行深层复制,则必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有所述的ShallowCopy方法来创建完整的克隆.这很简单:总共只有几行,请参阅下面的演示代码.

以下是显示相对性能差异的代码输出(深度嵌套的MemberwiseCopy为4.77秒,序列化为39.93秒).使用嵌套的MemberwiseCopy几乎与复制结构一样快,并且复制结构非常接近.NET能够达到的理论最大速度.

    Demo of shallow and deep copy, using classes and MemberwiseClone:
      Create Bob
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Clone Bob >> BobsSon
      Adjust BobsSon details
        BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
      Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Elapsed time: 00:00:04.7795670,30000000
    Demo of shallow and deep copy, using structs and value copying:
      Create Bob
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Clone Bob >> BobsSon
      Adjust BobsSon details:
        BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
      Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
        Bob.Age=30, Bob.Purchase.Description=Lamborghini
      Elapsed time: 00:00:01.0875454,30000000
    Demo of deep copy, using class and serialize/deserialize:
      Elapsed time: 00:00:39.9339425,30000000

要了解如何使用MemberwiseCopy执行深层复制,以下是演示项目:

// Nested MemberwiseClone example. 
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
    public Person(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    [Serializable] // Not required if using MemberwiseClone
    public class PurchaseType
    {
        public string Description;
        public PurchaseType ShallowCopy()
        {
            return (PurchaseType)this.MemberwiseClone();
        }
    }
    public PurchaseType Purchase = new PurchaseType();
    public int Age;
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person ShallowCopy()
    {
        return (Person)this.MemberwiseClone();
    }
    // Add this if using nested MemberwiseClone.
    // This is a class, which is a reference type, so cloning is more difficult.
    public Person DeepCopy()
    {
            // Clone the root ...
        Person other = (Person) this.MemberwiseClone();
            // ... then clone the nested class.
        other.Purchase = this.Purchase.ShallowCopy();
        return other;
    }
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
    public PersonStruct(int age, string description)
    {
        this.Age = age;
        this.Purchase.Description = description;
    }
    public struct PurchaseType
    {
        public string Description;
    }
    public PurchaseType Purchase;
    public int Age;
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct ShallowCopy()
    {
        return (PersonStruct)this;
    }
    // This is a struct, which is a value type, so everything is a clone by default.
    public PersonStruct DeepCopy()
    {
        return (PersonStruct)this;
    }
}
// Added only for a speed comparison.
public class MyDeepCopy
{
    public static T DeepCopy(T obj)
    {
        object result = null;
        using (var ms = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(ms, obj);
            ms.Position = 0;
            result = (T)formatter.Deserialize(ms);
            ms.Close();
        }
        return (T)result;
    }
}

然后,从main调用demo:

    void MyMain(string[] args)
    {
        {
            Console.Write("Demo of shallow and deep copy, using classes and MemberwiseCopy:\n");
            var Bob = new Person(30, "Lamborghini");
            Console.Write("  Create Bob\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
            Console.Write("  Clone Bob >> BobsSon\n");
            var BobsSon = Bob.DeepCopy();
            Console.Write("  Adjust BobsSon details\n");
            BobsSon.Age = 2;
            BobsSon.Purchase.Description = "Toy car";
            Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
            Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
            Debug.Assert(Bob.Age == 30);
            Debug.Assert(Bob.Purchase.Description == "Lamborghini");
            var sw = new Stopwatch();
            sw.Start();
            int total = 0;
            for (int i = 0; i < 100000; i++)
            {
                var n = Bob.DeepCopy();
                total += n.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
        }
        {               
            Console.Write("Demo of shallow and deep copy, using structs:\n");
            var Bob = new PersonStruct(30, "Lamborghini");
            Console.Write("  Create Bob\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
            Console.Write("  Clone Bob >> BobsSon\n");
            var BobsSon = Bob.DeepCopy();
            Console.Write("  Adjust BobsSon details:\n");
            BobsSon.Age = 2;
            BobsSon.Purchase.Description = "Toy car";
            Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
            Console.Write("  Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);                
            Debug.Assert(Bob.Age == 30);
            Debug.Assert(Bob.Purchase.Description == "Lamborghini");
            var sw = new Stopwatch();
            sw.Start();
            int total = 0;
            for (int i = 0; i < 100000; i++)
            {
                var n = Bob.DeepCopy();
                total += n.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
        }
        {
            Console.Write("Demo of deep copy, using class and serialize/deserialize:\n");
            int total = 0;
            var sw = new Stopwatch();
            sw.Start();
            var Bob = new Person(30, "Lamborghini");
            for (int i = 0; i < 100000; i++)
            {
                var BobsSon = MyDeepCopy.DeepCopy(Bob);
                total += BobsSon.Age;
            }
            Console.Write("  Elapsed time: {0},{1}\n", sw.Elapsed, total);
        }
        Console.ReadKey();
    }

同样,请注意,如果您使用嵌套的MemberwiseClone进行深层复制,则必须为类中的每个嵌套级别手动实现ShallowCopy,并使用DeepCopy调用所有所述的ShallowCopy方法来创建完整的克隆.这很简单:总共只有几行,请参阅上面的演示代码.

请注意,在克隆对象时,"struct"和"class"之间存在很大差异:

如果你有一个"struct",它是一个值类型,所以你可以复制它,然后克隆内容.

如果你有一个"类",它是一个引用类型,所以如果你复制它,你所做的只是将指针复制到它.要创建真正的克隆,您必须更具创造性,并使用在内存中创建原始对象的另一个副本的方法.

错误地克隆对象可能导致非常难以确定的错误.在生产代码中,我倾向于实现校验和以仔细检查对象是否已正确克隆,并且没有被另一个对它的引用破坏.可以在释放模式下关闭此校验和.

我发现这个方法非常有用:通常,你只想克隆对象的一部分,而不是整个事物.对于修改对象,然后将修改后的副本送入队列的任何用例,它也是必不可少的.

更新

可能使用反射以递归方式遍历对象图以进行深层复制.WCF使用此技术序列化对象,包括其所有子对象.诀窍是使用使其可被发现的属性来注释所有子对象.但是,您可能会失去一些性能优势.

更新

引用独立速度测试(见下面的评论):

我使用Neil的序列化/反序列化扩展方法,Contango的嵌套MemberwiseClone,Alex Burtsev的基于反射的扩展方法和Aut​​oMapper,每个100万次运行我自己的速度测试.Serialize-deserialize最慢,耗时15.7秒.然后是AutoMapper,耗时10.1秒.基于反射的方法要快2.4秒,速度要快得多.到目前为止,最快的是嵌套的MemberwiseClone,耗时0.1秒.归结为性能与为每个类添加代码以克隆它的麻烦.如果表现不是问题,请选择Alex Burtsev的方法. - Simon Tewsi


我可以确认这比序列化方法快得多.成本是:编写更多代码; 添加字段而不将其添加到克隆方法的维护风险; 需要为任何第三方类(如Dictionary <>)编写辅助类
Java和.NET都没有区分包含身份,可变状态,两者或两者都没有的引用.从概念上讲,应该只有一种类型的"克隆":一个新对象,其中每个引用封装与原始对应引用中相同的东西.如果引用封装了identity,则clone的引用必须引用*same*对象.如果它封装了可变状态但是*不是*identity,那么克隆必须接收对具有相同状态的不同对象的引用[否则两个引用都会错误地...
......封装身份以及国家].无法克隆封装身份和状态的对象引用,除非通过复制其他所有*来保存对该对象的引用* - 这通常很难或不可能.虽然对某些类型的对象的引用通常用于封装标识,但对其他对象的引用通常会封装可变状态,因此知道对象的类型不足以满足引用的目的.

5> 小智..:

我相信BinaryFormatter方法相对较慢(这让我感到惊讶!).如果符合ProtoBuf的要求,您可以将ProtoBuf .NET用于某些对象.从ProtoBuf入门页面(http://code.google.com/p/protobuf-net/wiki/GettingStarted):

支持的类型说明:

自定义类:

被标记为数据合同

有一个无参数的构造函数

对于Silverlight:是公开的

许多常见的原语等

维数组:T []

列出/IList

字典/IDictionary

任何实现IEnumerable 并具有Add(T)方法的类型

该代码假定类型将在当选成员周围变化.因此,不支持自定义结构,因为它们应该是不可变的.

如果您的班级符合这些要求,您可以尝试:

public static void deepCopy(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        Serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = Serializer.Deserialize(stream);
    }
}

这确实非常快......

编辑:

这是修改此代码的工作代码(在.NET 4.6上测试).它使用System.Xml.Serialization和System.IO.无需将类标记为可序列化.

public void DeepCopy(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        var serializer = new XS.XmlSerializer(typeof(T));

        serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = (T)serializer.Deserialize(stream);
    }
}



6> Suresh Kumar..:

你可以试试这个

    public static object DeepCopy(object obj)
    {
        if (obj == null)
            return null;
        Type type = obj.GetType();

        if (type.IsValueType || type == typeof(string))
        {
            return obj;
        }
        else if (type.IsArray)
        {
            Type elementType = Type.GetType(
                 type.FullName.Replace("[]", string.Empty));
            var array = obj as Array;
            Array copied = Array.CreateInstance(elementType, array.Length);
            for (int i = 0; i < array.Length; i++)
            {
                copied.SetValue(DeepCopy(array.GetValue(i)), i);
            }
            return Convert.ChangeType(copied, obj.GetType());
        }
        else if (type.IsClass)
        {

            object toret = Activator.CreateInstance(obj.GetType());
            FieldInfo[] fields = type.GetFields(BindingFlags.Public |
                        BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                object fieldValue = field.GetValue(obj);
                if (fieldValue == null)
                    continue;
                field.SetValue(toret, DeepCopy(fieldValue));
            }
            return toret;
        }
        else
            throw new ArgumentException("Unknown type");
    }

感谢DetoX83 关于代码项目的文章.

推荐阅读
mobiledu2402851203
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有