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

How do you do a deep copy of an object in .NET (C# specifically)?

本问题已经有最佳答案,请猛点这里访问。

我想要一份真正的深度拷贝。在爪哇,这很容易,但是你在C是怎么做到的呢?


我已经看到了几种不同的方法,但是我使用了一种通用的实用方法,例如:

1
2
3
4
5
6
7
8
9
10
11
public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

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

笔记:

  • 您的类必须标记为[Serializable],才能工作。
  • 源文件必须包含以下代码:

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


我基于递归的"memberwiseClone"编写了一个深度对象复制扩展方法。它的速度很快(比BinaryFormatter快三倍),可以与任何对象一起工作。您不需要默认的构造函数或可序列化的属性。

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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<Object, Object>(new ReferenceEqualityComparer()));
        }
        private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> 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<object, object> 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<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> 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<T>(this T original)
        {
            return (T)Copy((Object)original);
        }
    }

    public class ReferenceEqualityComparer : EqualityComparer<Object>
    {
        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<Array, int[]> 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;
            }
        }
    }

}


基于基尔霍夫的解决方案…

使用C 3.0,可以创建如下扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class ExtensionMethods
{
    // Deep clone
    public static T DeepClone<T>(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方法标记为[可序列化]的任何类

1
MyClass copy = obj.DeepClone();


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

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

下面是显示相对性能差异的代码输出(深度嵌套的memberwisecopy为4.77秒,序列化为39.93秒)。使用嵌套的MeistWistCopp几乎和复制一个结构一样快,并且复制一个StULTE与理论上的最大速度.NET非常接近,这可能相当接近C或C++中相同的东西的速度(但是必须运行一些等价的基准来检查这个请求)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    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进行深度复制,下面是演示项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 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>(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调用演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
    void MyMain(string[] args)
    {
        {
            Console.Write("Demo of shallow and deep copy, using classes and MemberwiseCopy:
"
);
            var Bob = new Person(30,"Lamborghini");
            Console.Write("  Create Bob
"
);
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}
"
, Bob.Age, Bob.Purchase.Description);
            Console.Write("  Clone Bob >> BobsSon
"
);
            var BobsSon = Bob.DeepCopy();
            Console.Write("  Adjust BobsSon details
"
);
            BobsSon.Age = 2;
            BobsSon.Purchase.Description ="Toy car";
            Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}
"
, 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:
"
);
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}
"
, 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}
"
, sw.Elapsed, total);
        }
        {              
            Console.Write("Demo of shallow and deep copy, using structs:
"
);
            var Bob = new PersonStruct(30,"Lamborghini");
            Console.Write("  Create Bob
"
);
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}
"
, Bob.Age, Bob.Purchase.Description);
            Console.Write("  Clone Bob >> BobsSon
"
);
            var BobsSon = Bob.DeepCopy();
            Console.Write("  Adjust BobsSon details:
"
);
            BobsSon.Age = 2;
            BobsSon.Purchase.Description ="Toy car";
            Console.Write("    BobsSon.Age={0}, BobsSon.Purchase.Description={1}
"
, 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:
"
);
            Console.Write("    Bob.Age={0}, Bob.Purchase.Description={1}
"
, 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}
"
, sw.Elapsed, total);
        }
        {
            Console.Write("Demo of deep copy, using class and serialize/deserialize:
"
);
            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<Person>(Bob);
                total += BobsSon.Age;
            }
            Console.Write("  Elapsed time: {0},{1}
"
, sw.Elapsed, total);
        }
        Console.ReadKey();
    }

同样,请注意,如果使用嵌套的memberwiseclone进行深度复制,则必须为类中的每个嵌套级别手动实现一个Shallowcopy,以及调用所有上述Shallowcopy方法以创建完整克隆的Deepcopy。这很简单:总共只有几行,请参见上面的演示代码。

请注意,在克隆对象时,"struct"和"class"有很大的区别:

  • 如果您有一个"struct",它是一个值类型,所以您可以复制它,内容将被克隆。
  • 如果你有一个"类",它是一个引用类型,所以如果你复制它,你所要做的就是复制指向它的指针。要创建一个真正的克隆,您必须更具创造性,并使用一种方法来创建内存中原始对象的另一个副本。
  • 不正确地克隆对象会导致很难找出错误。在生产代码中,我倾向于实现一个校验和来复查对象是否被正确克隆,并且没有被对它的另一个引用破坏。此校验和可在释放模式下关闭。
  • 我发现这个方法非常有用:通常,你只想复制对象的一部分,而不是整个对象。对于修改对象,然后将修改后的副本送入队列的任何用例来说,这也是必需的。

更新

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

更新

关于独立速度测试的报价(见以下评论):

I've run my own speed test using Neil's serialize/deserialize
extension method, Contango's Nested MemberwiseClone, Alex Burtsev's
reflection-based extension method and AutoMapper, 1 million times
each. Serialize-deserialize was slowest, taking 15.7 seconds. Then
came AutoMapper, taking 10.1 seconds. Much faster was the
reflection-based method which took 2.4 seconds. By far the fastest was
Nested MemberwiseClone, taking 0.1 seconds. Comes down to performance
versus hassle of adding code to each class to clone it. If performance
isn't an issue go with Alex Burtsev's method.
– Simon Tewsi


我相信BinaryFormatter方法比较慢(这让我很惊讶!)。如果某些对象满足ProtoBuf的要求,则可以将ProtoBuf.net用于这些对象。从protobuf入门页面(http://code.google.com/p/protobuf net/wiki/getting started):

有关支持类型的说明:

自定义类:

  • 标记为数据合同
  • 具有无参数构造函数
  • 对于Silverlight:是公共的
  • 许多公共原语等。
  • 一维数组:t[]
  • 列表
  • 字典/idictionary
  • 实现IEnumerable并具有add(t)方法的任何类型

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

如果你的课程符合这些要求,你可以尝试:

1
2
3
4
5
6
7
8
9
public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
{
    using (var stream = new MemoryStream())
    {
        Serializer.Serialize(stream, object2Copy);
        stream.Position = 0;
        objectCopy = Serializer.Deserialize<T>(stream);
    }
}

真的很快…

编辑:

这里是修改这个的工作代码(在.NET 4.6上测试)。它使用system.xml.serialization和system.io。无需将类标记为可序列化。

1
2
3
4
5
6
7
8
9
10
11
public void DeepCopy<T>(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);
    }
}


你可以试试这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    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");
    }

多亏了关于代码项目的第83篇文章。


也许你只需要一个简单的拷贝,在这种情况下使用Object.MemberWiseClone()

文件中对MemberWiseClone()的深入复制策略提出了很好的建议:

http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx


最好的方法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    public interface IDeepClonable<T> where T : class
    {
        T DeepClone();
    }

    public class MyObj : IDeepClonable<MyObj>
    {
        public MyObj Clone()
        {
            var myObj = new MyObj();
            myObj._field1 = _field1;//value type
            myObj._field2 = _field2;//value type
            myObj._field3 = _field3;//value type

            if (_child != null)
            {
                myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
            }

            int len = _array.Length;
            myObj._array = new MyObj[len]; // array / collection
            for (int i = 0; i < len; i++)
            {
                myObj._array[i] = _array[i];
            }

            return myObj;
        }

        private bool _field1;
        public bool Field1
        {
            get { return _field1; }
            set { _field1 = value; }
        }

        private int _field2;
        public int Property2
        {
            get { return _field2; }
            set { _field2 = value; }
        }

        private string _field3;
        public string Property3
        {
            get { return _field3; }
            set { _field3 = value; }
        }

        private MyObj _child;
        private MyObj Child
        {
            get { return _child; }
            set { _child = value; }
        }

        private MyObj[] _array = new MyObj[4];
    }


msdn文档似乎暗示克隆应该执行深度复制,但从未明确说明:

icloneable接口包含一个成员clone,它旨在支持memberwiseclone提供的克隆之外的克隆…memberwiseclone方法创建一个浅副本…

你会发现我的帖子很有用。

http://pragmaticcoding.com/index.php/cloning-objects-in-c/


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    public static object CopyObject(object input)
    {
        if (input != null)
        {
            object result = Activator.CreateInstance(input.GetType());
            foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
            {
                if (field.FieldType.GetInterface("IList", false) == null)
                {
                    field.SetValue(result, field.GetValue(input));
                }
                else
                {
                    IList listObject = (IList)field.GetValue(result);
                    if (listObject != null)
                    {
                        foreach (object item in ((IList)field.GetValue(input)))
                        {
                            listObject.Add(CopyObject(item));
                        }
                    }
                }
            }
            return result;
        }
        else
        {
            return null;
        }
    }

这种方式比BinarySerialization快几倍,而且不需要[Serializable]属性。


我有一个简单的想法。将LINQ与新选择一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Fruit
{
  public string Name {get; set;}
  public int SeedCount {get; set;}
}

void SomeMethod()
{
  List<Fruit> originalFruits = new List<Fruit>();
  originalFruits.Add(new Fruit {Name="Apple", SeedCount=10});
  originalFruits.Add(new Fruit {Name="Banana", SeedCount=0});

  //Deep Copy
  List<Fruit> deepCopiedFruits = from f in originalFruits
              select new Fruit {Name=f.Name, SeedCount=f.SeedCount};
}