Many times, we copy an object instance A to instance B. When using instance B to do other things, we will modify instance B. In order to ensure that the modification of B will not affect the normal use of A, deep copy is needed.
I searched for some deep copy methods online and wrote several sets of examples to test these methods.
My OS version is Win7 Ultimate, and the .NET Framework version is 4.5
Testing procedures
I have built a C# form application (Winform), and the content of the Load function of its main window FormMain is as follows:
private void FormMain_Load(object sender, EventArgs e) { //Test 1: Deep Copy Custom Class try { ("=== Deep copy Custom Classes ==="); TestClass test1 = new TestClass(); = 10; = "hello world!"; = new string[] { "x", "y", "z" }; TestClass test2 = new TestClass(); = 11; = "hello world2!"; = new string[] { "i", "j", "k" }; = test2; ("---test1_start---"); (test1); ("---test1_end---"); TestClass test3 = (TestClass)(test1); ("---test3_start---"); (test3); ("---test3_end---"); } catch (Exception ex) { (()); }
//Test 2: Deep Copy Serializable Custom Class
try { ("=== Deep copy Serializable custom classes ==="); TestClassWithS test1 = new TestClassWithS(); = 10; = "hello world!"; = new string[] { "x", "y", "z" }; TestClassWithS test2 = new TestClassWithS(); = 11; = "hello world2!"; = new string[] { "i", "j", "k" }; = test2; ("---test1_start---"); (test1); ("---test1_end---"); TestClassWithS test3 = (TestClassWithS)(test1); ("---test3_start---"); (test3); ("---test3_end---"); } catch (Exception ex) { (()); }
//Test 3: Deep Copy DataTable
try { ("=== Deep copy DataTable ==="); DataTable dtKirov = new DataTable("TestTable"); ("Col1"); ("Col2"); ("Col3"); ("1-1", "1-2", "1-3"); ("2-1", "2-2", "2-3"); ("3-1", "3-2", "3-3"); ("=== Before copying ==="); for (int i = 0; i < ; i++) { ([i].ColumnName + "\t"); } ("\n-----------------"); for (int i = 0; i < ; i++) { for (int j = 0; j < ; j++) { ([i][j].ToString() + "\t"); } (); } (); DataTable dtDreadNought = (DataTable)(dtKirov); ("=== After copying ==="); for (int i = 0; i < ; i++) { ([i].ColumnName + "\t"); } ("\n-----------------"); for (int i = 0; i < ; i++) { for (int j = 0; j < ; j++) { ([i][j].ToString() + "\t"); } (); } (); } catch (Exception ex) { (()); }
//Test 4: Deep Copy TextBox
try { ("=== Deep copy TextBox ==="); = "1234"; ("Before copy:" + ); TextBox txtTmp = new TextBox(); txtTmp = (TextBox)(txtTest); ("After copying:" + ); } catch (Exception ex) { (()); }
//Test 5: Deep Copy DataGridView
try { ("=== Deep copy DataGridView ==="); DataGridView dgvTmp = new DataGridView(); dgvTmp = (DataGridView)(dgvTest); } catch (Exception ex) { (()); } }
txtTest is a Test TextBox, dgvTmp is a Test DataGridView, TestClass is a Custom Class, TestClassWithS is a TestClass class with Serializable features. Their specific implementations are as follows:
using System; using ; using ; using ; namespace DataCopyTest { public class TestClass { public int a; public string b; public string[] c; public TestClass d; public override string ToString() { string s = "a:" + a + "\n"; if (b != null) { s += "b:" + b + "\n"; } if (c != null) { foreach (string tmps in c) { if (!(tmps)) { s += "c:" + tmps + "\n"; } } } if (d != null) { s += (); } return s; } } //Support serialization TestClass [Serializable] public class TestClassWithS { public int a; public string b; public string[] c; public TestClassWithS d; public override string ToString() { string s = "a:" + a + "\n"; if (b != null) { s += "b:" + b + "\n"; } if (c != null) { foreach (string tmps in c) { if (!(tmps)) { s += "c:" + tmps + "\n"; } } } if (d != null) { s += (); } return s; } } }
I used these five classes to conduct deep copy tests for each searched deep copy method. The characteristics of these five classes are as follows:
I. Deep copy test of custom class TestClass
II. Deep copy test of the custom class TestClassWithS. TestClassWithS is a TestClass class with Serializable features added.
III. Deep copy test of DataTable
IV. Deep copy test of control TextBox
V. Deep copy test of control DataGridView
We test by implementing the method
Test deep replication method 1
Deep copy objects using serialization and deserialization of binary streams
public static object DeepCopyObject(object obj) { BinaryFormatter Formatter = new BinaryFormatter(null, new StreamingContext()); MemoryStream stream = new MemoryStream(); (stream, obj); = 0; object clonedObj = (stream); (); return clonedObj; }
The test results for five scenarios are:
I. Triggered the exception SerializationException because this class does not support serialization
The first chance exception of type "" occurs in
: Type "" in the assembly "DataCopyTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" is not marked as serializable.
In (RuntimeType type)
In (Type type, StreamingContext context)
exist ()
In (Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
In (Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, S"" (hosted (v4.0.30319)): Loaded "C:\Windows\\assembly\GAC_MSIL\\v4.0_4.0.0.0__b77a5c561934e089\"
erializationBinder binder)
In (Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
In (Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
In (Stream serializationStream, Object graph)
In (Object obj) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 24
In .FormMain_Load(Object sender, EventArgs e) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 37
II. Can be copied normally (√)
III. Can be copied normally (√)
IV, triggering an exception SerializationException, because this class does not support serialization
The first chance exception of type "" occurs in
: Type "" in assembly ", Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" is not marked as serializable.
In (RuntimeType type)
In (Type type, StreamingContext context)
exist ()
In (Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
In (Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
In (Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
In (Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
In (Stream serializationStream, Object graph)
In (Object obj) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 24
In .FormMain_Load(Object sender, EventArgs e) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 128
V. SerializationException triggered because this class does not support serialization
The first chance exception of type "" occurs in
: Type "" in assembly ", Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" is not marked as serializable.
In (RuntimeType type)
In (Type type, StreamingContext context)
exist ()
In (Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
In (Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder)
In (Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
In (Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
In (Stream serializationStream, Object graph)
In (Object obj) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 24
In .FormMain_Load(Object sender, EventArgs e) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 141
Conclusion: Deeply copy an object using the method of serialization and deserialization to binary streams. It can only be used if the object supports the Serializable feature.
Test deep replication method 2
public static object DeepCopyObject(object obj) { Type t = (); PropertyInfo[] properties = (); Object p = ("", , null, obj, null); foreach (PropertyInfo pi in properties) { if () { object value = (obj, null); (p, value, null); } } return p; }
The test results for five scenarios are:
I. The exception will not be triggered, but the result is completely wrong
II. The exception will not be triggered, but the result is completely wrong
III. The exception will not be triggered, but the result is completely wrong
The IV and Text fields assignment results are correct, but other contents cannot be guaranteed.
V, trigger exception ArgumentOutOfRangeException, TargetInvocationException
The first chance exception of type "" occurs in
The first chance exception of type "" occurs in
: An exception occurred in the call target. ---> : The specified parameter has exceeded the range of valid values.
Parameter name: value
In .set_FirstDisplayedScrollingColumnIndex(Int32 value)
--- The end of the internal exception stack trace ---
In (Object target, Object[] arguments, Signature sig, Boolean constructor)
In (Object obj, Object[] parameters, Object[] arguments)
In (Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
In (Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
In (Object obj, Object value, Object[] index)
In (Object obj) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 29
In .FormMain_Load(Object sender, EventArgs e) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 141
Conclusion: Using this method for so-called deep replication is completely self-destructive!
Test deep replication method 3
public static object DeepCopyObject(object obj) { if (obj != null) { object result = (()); foreach (FieldInfo field in ().GetFields()) { if (("IList", false) == null) { (result, (obj)); } else { IList listObject = (IList)(result); if (listObject != null) { foreach (object item in ((IList)(obj))) { (DeepCopyObject(item)); } } } } return result; } else { return null; } }
The test results for five scenarios are:
I. Can be copied normally (√)
II. Can be copied normally (√)
III. The exception was not triggered, and there is no row in DataTable after copying.
IV, no exception was triggered, Text field not assigned
V. No exception triggered
Conclusion: This method is only suitable for deep copying classes with simple structures (such as only basic fields, arrays, etc. in the class). Deep copying can also be performed for objects that do not support serialization.
Test deep replication method 4
This code is the same as method 3
public static object DeepCopyObject(object obj) { if (obj == null) return null; Type type = (); if ( || type == typeof(string)) { return obj; } else if () { Type elementType = ( ("[]", )); var array = obj as Array; Array copied = (elementType, ); for (int i = 0; i < ; i++) { (DeepCopyObject((i)), i); } return (copied, ()); } else if () { object toret = (()); FieldInfo[] fields = ( | | ); foreach (FieldInfo field in fields) { object fieldValue = (obj); if (fieldValue == null) continue; (toret, DeepCopyObject(fieldValue)); } return toret; } else throw new ArgumentException("Unknown type"); }
The test results for five scenarios are:
I. Can be copied normally (√)
II. Can be copied normally (√)
III. Trigger the exception MissingMethodException
The first chance exception of type "" occurs in
: No constructor without parameters is defined for this object.
In (RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
In (Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
In (Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
In (Type type, Boolean nonPublic)
In (Type type)
In (Object obj) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 45
In (Object obj) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 53
In .FormMain_Load(Object sender, EventArgs e) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 99
IV. The exception was not triggered, but the Text field was not assigned successfully.
V. Trigger exception MissingMethodException
The first chance exception of type "" occurs in
: No constructor without parameters is defined for this object.
In (RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
In (Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
In (Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
In (Type type, Boolean nonPublic)
In (Type type)
In (Object obj) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 45
In (Object obj) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 53
In .FormMain_Load(Object sender, EventArgs e) location d:\MyPrograms\DataCopyTest\DataCopyTest\:Line number 141
Conclusion: The function of this method is similar to that of Method 3, and can only deeply copy the classes composed of basic data types.
Specific analysis of specific issues
From the example above, it can be seen that it is difficult to find a one-size-fits-all way to deeply copy all objects. Some deep copy methods that use high-level language features (reflection) cannot be fully confident in all classes even if they can be successfully tried on some classes. Therefore, I think the following method should be used to deal with the deep copying of objects:
1. For classes composed of basic data types, label them with Serializable and use the serialization and deserialization methods for deep copying.
2. Other more complex types such as DataGridView can be written to write a method according to your own situation for deep copying. The reason why I said here to write a method according to your own situation is that when copying many classes, you only need to copy the attributes that are useful to you. For example, in the TextBox control, only Text is useful to you. If you need to use the values of Readonly and other attributes in the copied object, then you can also add the assignment of these attributes to the copy method you implement. Another advantage of this is that it facilitates some customized development.
For example, the following code is an approximate deep copy of the DataGridView. This code copies the content of a DataGridView (dgv) to another DataGridView (dgvTmp), and then passes the dgvTmp to the related function to output the content of the DataGridView to the Excel document:
DataGridView dgvTmp = new DataGridView(); = false; //User-generated lines are not allowed, otherwise the last line will appear after exportingfor (int i = 0; i < ; i++) { ([i].Name, [i].HeaderText); if ([i].("N")) //Make the export of Excel document amount column for SUM operation { [i]. = [i].; } if (![i].Visible) { [i].Visible = false; } } for (int i = 0; i < ; i++) { object[] objList = new object[[i].]; for (int j = 0; j < ; j++) { if ([j].("N")) { objList[j] = [i].Cells[j].Value; //Make the export of Excel document amount column for SUM operation } else { objList[j] = [i].Cells[j].EditedFormattedValue; //Export the data dictionary by display text } } (objList); }
The characteristics of this code are as follows:
1. The DataGridView property AllowUserToAddRows must be set to false. Otherwise, after exporting to Excel document, you will find that there will be an extra blank line in the end.
2. We marked those columns here as hidden columns. In the subsequent processing, if you want to delete these columns, the deleted columns of dgvTmp instead of dgv columns, protecting the original data.
3. For the translation of some data dictionaries, we are not passing on Value but EditedFormattedValue. This method directly uses the translated text displayed by dgv on the screen, rather than the original data dictionary value.
4. For some amount columns, you need to directly pass the Value value, and you need to set the column, so that these contents can be summed after they are later output to the Excel document (summarization of strings similar to "12,345.67" in Excel cannot be summed).