What happen if the object have event, Do they lost everything because of the serialization?
–
Patrick DesjardinsSep 24 '08 at 19:56

4

Event subscribes are included into serialization graph, since BinaryFormatter uses fields via reflection, and events are just fields of delegate types plus add/remove/invoke methods. You can use [field: NonSerialized] on event to avoid this.
–
Ilya RyzhenkovSep 24 '08 at 20:16

23

This only works if all members are marked [Serializable]
–
ChristopherSep 30 '08 at 15:06

@Amir - it isn't necessary for the class to implement ISerializable, Marking with SerializableAttribute is sufficient. The attribute uses reflection to perform serialization, while the interface allows you to write a custom serializer
–
NeilFeb 14 '11 at 16:10

9

I agree with your statement, but I like Amir's suggestion b/c it provides compile-time checking. Is there any way to reconcile the two?
–
Michael BlackburnMar 30 '11 at 19:47

I wrote deep object copy extension method, based on recursive "MemberwiseClone",
it is fast (3 times faster then BinaryFormatter), it works with any object, you don't need default constructor or serializable attributes.

You can use Nested MemberwiseClone to do a deep copy. Its almost the same speed as copying a value struct, and its an order of magnitude faster than (a) reflection or (b) serialization (as described in other answers on this page).

Note that if you use Nested MemberwiseClone for a deep copy, you have to manually implement a ShallowCopy for each nested level in the class, and a DeepCopy which calls all said ShallowCopy methods to create a complete clone. This is simple: only a few lines in total, see the demo code below.

Here is the output of the code showing the relative performance difference (4.77 seconds for deep nested MemberwiseCopy vs. 39.93 seconds for Serialization). Using nested MemberwiseCopy is almost as fast as copying a struct, and copying a struct is pretty darn close to the theoretical maximum speed .NET is capable of.

To understand how to do a deep copy using MemberwiseCopy, here is the demo project:

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

Again, note that if you use Nested MemberwiseClone for a deep copy, you have to manually implement a ShallowCopy for each nested level in the class, and a DeepCopy which calls all said ShallowCopy methods to create a complete clone. This is simple: only a few lines in total, see the demo code above.

Note that when it comes to cloning an object, there is is a big difference between a "struct" and a "class":

If you have a "struct", it's a value type so you can just copy it, and the contents will be cloned.

If you have a "class", it's a reference type, so if you copy it, all you are doing is copying the pointer to it. To create a true clone, you have to be more creative, and use a method which creates another copy of the original object in memory.

Cloning objects incorrectly can lead to very difficult-to-pin-down bugs. In production code, I tend to implement a checksum to double check that the object has been cloned properly, and hasn't been corrupted by another reference to it. This checksum can be switched off in Release mode.

I find this method quite useful: often, you only want to clone parts of the object, not the entire thing. It's also essential for any use case where you are modifying objects, then feeding the modified copies into a queue.

Update

It's probably possible to use reflection to recursively walk through the object graph to do a deep copy. WCF uses this technique to serialize an object, including all of its children. The trick is to annotate all of the child objects with an attribute that makes it discoverable. You might lose some performance benefits, however.

Good post. Any idea why serialization is so much slower? Also, how would your checksum work? Why not just have an equality checker?
–
user420667Apr 1 '12 at 15:33

3

I can confirm that this is much faster than the serialization method. The cost is: writing more code; the maintenance risk of adding a field without adding it to the clone method; need to write helper classes for any 3rd party classes (such as Dictionary<>)
–
NeilMay 23 '12 at 14:37

It's too bad neither Java nor .NET distinguishes among references that encapsulate identity, mutable state, both, or neither. Conceptually, there should only be one type of "clone": a new object where each reference encapsulates the same thing as in the corresponding reference in the original. If a reference encapsulates identity, the clone's reference must refer to the same object. If it encapsulates mutable state but not identity, the clone must receive a reference to a different object with the same state [otherwise both references would erroneously...
–
supercatSep 23 '13 at 19:53

3

...encapsulate identity as well as state]. An object reference that encapsulates both identity and state cannot be cloned except by copying everything else which holds a reference to that object--a feat which is often difficult or impossible. While references to some types of object will usually be used to encapsulate identity, and references to others will usually encapsulate mutable state, knowing the type of an object is not sufficient to the purpose for which a reference is held.
–
supercatSep 23 '13 at 20:01

I believe that the BinaryFormatter approach is relatively slow (which came as a surprise to me!). You might be able to use ProtoBuf .Net for some objects if they meet the requirements of ProtoBuf. From the ProtoBuf Getting Started page (http://code.google.com/p/protobuf-net/wiki/GettingStarted):

Notes on types supported:

custom classes that:

are marked as data-contract

have a parameterless constructor

for Silverlight: are public

many common primitives etc

single dimension arrays: T[]

List / IList

Dictionary / IDictionary

any type which implements IEnumerable and has an Add(T) method

The code assumes that types will be mutable around the elected members. Accordingly, custom structs are not supported, since they should be immutable.

It's not simpler when you have mutable reference types, many properties, lists with sublists, etc. This can work in a few simple scenarios, but is still error-prone when your Fruit class adds another property and you forget to change your method.
–
Nelson RothermelFeb 4 '13 at 19:32