-
Notifications
You must be signed in to change notification settings - Fork 1
Serialization
Serialization is the process of persisting the state of an object to a file or memory buffer. The persisted data contains all the necessary information you need to reconstruct (deserialize) the state of the object.
Serializable
marks a class serializable.
[Serializable]
public class UserPrefs
{
public string WindowColor;
public int FontSize;
[NotSerialized]
private int passcode;
}
Example using BinaryFormatter
to write a Serializable
object to a file stream.
UserPrefs userData = new UserPrefs { WindowColor = "Blue", FontSize = 24 };
BinaryFormatter bf = new BinaryFormatter();
using (Stream fs = new File.OpenWrite("user.dat"))
{
bf.Serialize(fs, userData);
}
All classes in a hierarchy must be marked as serializable, otherwise using types like BinaryFormatter
or SoapFormatter
will throw a SerializationException
at runtime.
The CLR accounts for all related objects as well to ensure that the data is persisted correctly. It does this by building an object graph.
(3: Car) -> (2: Radio)
(1: SpecialCar) -> (3: Car)
(1: SpecialCar) -> (2: Radio)
This is one way of representing a graph with three nodes, where: "Car" depends on "Radio", and "SpecialCar" depends on both "Car" and "Radio".
The CLR may represent this as such (specifics not certain from source material):
[Car 3, ref 2], [Radio 2], [SpecialCar 1, ref 3, ref 2]
.NET provides some types to do serialization.
-
BinaryFormatter
inSystem.Runtime.Serialization.Formatters.Binary
-
SoapFormatter
inSystem.Runtime.Serialization.Formatters.Soap
-
XmlSerializer
inSystem.Xml.Serialization
The first two serialize all fields of an object. The XML formatter, on the other hand, only serializes public fields.
Both the binary and SOAP formatters implement the IFormatter
and IRemotingFormatter
interfaces.
public interface IFormatter
{
SerializationBinder Binder { get; set; }
StreamingContext Context { get; set; }
ISurrogateSelector SurrogateSelector { get; set; }
object Deserialize(Stream serializationStream);
void Serialize(Stream serializationStream, object graph);
}
public interface IRemotingFormatter : IFormatter
{
object Deserialize(Stream serializationStream, HeaderHandler handler);
void Serialize(Stream serializationStream, object graph, Header[] headers);
}
Most types in the System.Collections
namespace are serializable.
-
ISerializable
- You can implement this on a[Serializable]
type to control its serialization. -
ObjectIDGenerator
- Generates IDs for members in an object graph. -
[OnDeserialized]
- Mark method to be called after deserialization. -
[OnDeserializing]
- Mark method to be called before deserialization. -
[OnSerialized]
- Mark method to be called on after serialization. -
[OnSerializing]
- Mark method to be called on before serialization. -
[OptionalField]
- Mark field that can be missing. Used for version tolerance, where if a field is missing in a new version of the type it can be safely ignored. -
SerializationInfo
- This class is essentially a dictionary, or property bag, holding name-value pairs of the type's state data to be serialized.
This interface is very simple. GetObjectData()
is called by the formatter.
public interface ISerializable
{
void GetObjectData(SerializationInfo info, StreamingContext context);
}
If the ISerializable
interface is used, a special constructor must also be defined.
[Serializable]
class SomeClass : ISerializable
{
protected SomeClass(SerializationInfo info, StreamingContext ctx) { /* ... */ }
}
When the BinaryFormatter
serializes an object, the following information is used:
- The fully qualified name of the objects in the graph (e.g. MyApp.MyClass).
- The name of the assembly defining the object graph (e.g. MyApp.exe).
- An instance of the
SerializationInfo
class that contains all stateful data maintained by the members in the object graph.
The SoapFormatter
uses the information similarly, but the XmlSerializer
ignores the fully qualified name and assembly name in order to increase portability of the data.
Formatters also analyze the members in the object graph:
- A check is made to determine whether the object is marked with the
[Serializable]
attribute. If the object is not, aSerializationException
is thrown. - If the object is marked
[Serializable]
, a check is made to determine whether the object implements theISerializable
interface. If this is the case,GetObjectData()
is called on the object. - If the object does not implement
ISerializable
, the default serialization process is used, serializing all fields not marked[NotSerialized]
. - Other serialization attributes are also examined.
This example serializes strings out in uppercase, and reconstructs the object with lowercase strings.
[Serializable]
class MySpecialClass : ISerializable
{
private string A;
private string B;
public MySpecialClass() {}
protected MySpecialClass(SerializationInfo info, StreamingContext ctx)
{
A = info.GetString("str_A").ToLower();
B = info.GetString("str_B").ToLower();
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext ctx)
{
info.AddValue("str_A", A.ToUpper());
info.AddValue("str_B", B.ToUpper());
}
}
Aside from the above example, you can customize serialization with only the serialization lifecycle attributes: [OnSerialized]
, [OnDeserializing]
, etc.
These attributes require the method to have a specific signature.
[OnSerializing]
private void OnSerializing(StreamingContext ctx)
{
A = A.ToUpper(); // The downside is that this overwrites the value in the instance.
B = B.ToUpper(); // But in many serialization cases, that's totally fine.
}
- Abstract Classes
- Access Modifiers
- Anonymous Methods
- Anonymous Types
- Arrays
- Attributes
- Console I/O
- Constructors
- Const Fields
- Delegates
- Enums
- Exceptions
- Extension Methods
- File IO
- Generics
- Interfaces
- Iterators
- LINQ
- Main
- Null Operators
- Parameters
- Polymorphism
- Virtual Functions
- Reflection
- Serialization
- Strings
- Value Types
- "Base" Keyword
- "Is" and "As"
- "Sealed" Keyword
- nameof expression