Section 9.1. Automatic Serialization


9.1. Automatic Serialization

.NET implements the automatic serialization of objects by means of reflection, a simple and elegant technique that uses metadata exposed by every .NET component. Reflection is discussed in detail in Appendix C. .NET can capture the value of every one of an object's fields and serialize it to memory, to a file, or to a network connection. .NET also supports automatic deserialization: .NET can create a new object, read its persisted field values, and, using reflection, set the values of its fields. Because reflection can access private fields, including base-class fields, .NET can take a complete snapshot of the state of an object during serialization and perfectly reconstruct that state during deserialization. Another advantage of reflection-based serialization is that the code used by .NET is completely general-purposethe state of every .NET type can be read or set using reflection.

.NET serializes the object state into a stream. A stream is a logical sequence of bytes, independent of any particular medium (file, memory, communication port, or other resource). This extra level of indirection means that you can use the same serialization infrastructure with any medium, simply by selecting an appropriate stream type. The various stream types provided by .NET all derive from the abstract class Stream, defined in the System.IO namespace. Although you need a Stream instance to serialize and deserialize an object, there is usually no need to interact explicitly with the methods and properties of the Stream itself.

.NET serialization is object-based. As a result, only instance fields are serialized. Static fields are excluded from serialization.


9.1.1. The Serializable Attribute

By default, user-defined types (classes and structs) aren't serializable. The reason is that .NET has no way of knowing whether a reflection-based dump of the object state to a stream makes sense. Perhaps the object members have some transient value or state (such as an open database connection or communication port). If .NET simply serialized the state of such an object, when you constructed a new object by deserializing it from the stream you would end up with a defective object. Consequently, serialization can be performed only with the developer's consent.

Enumerations are always serializable.


To indicate to .NET that instances of your class are serializable, you can add the Serializable attribute to your class definition. For example:

     [Serializable]     public class MyClass     {         public string SomeString;         public int SomePublicNumber;         int m_SomePrivateNumber;         /* Methods and properties */     }

In most cases, decorating a user-defined type definition with the Serializable attribute is all you need to do. If the class has member variables that are complex types themselves, .NET automatically serializes and deserializes these members as well:

     [Serializable]     public class MyOtherClass     {...}     [Serializable]     public class MyClass     {         MyOtherClass m_Obj;         /* Methods and properties */     }

The result is recursive iteration over an object and all its contained objects. The object can be the root of a huge graph of interconnected objects, as shown in Figure 9-1.

Figure 9-1. .NET serialization traverses the entire object graph


Regardless of its depth, .NET captures the entire state of any graph and serializes it. The recursive traversal algorithm used by .NET is smart enough to detect cyclic references in the graph, tagging objects it has already visited and thereby avoiding processing the same object twice. This approach allows .NET to serialize complex data structures such as doubly linked lists.

9.1.2. Non-Serializable Members

When a class is serializable, .NET insists that all its member variables be serializable as well. If it discovers a non-serializable member, it throws an exception of type SerializationException during serialization. However, what if the class or a struct has a member that can't be serialized? That type will not have the Serializable attribute and will preclude the containing type from being serialized. Commonly, that non-serializable member is a reference type that requires some special initialization. The solution to this problem requires marking such a member as non-serializable and taking a custom step to initialize it during deserialization.

To allow a serializable type to contain a non-serializable type as a member variable, you need to mark the member with the NonSerialized field attribute:

     public class MyOtherClass     {...}     [Serializable]     public class MyClass     {             [NonSerialized]        MyOtherClass m_Obj;        /* Methods and properties */     }

When .NET serializes a member variable, it first reflects it to see whether it has the NonSerialized attribute. If so, .NET ignores that variable and simply skips over it. This allows you to preclude from serialization even serializable types:

     [Serializable]     public class MyClass     {             [NonSerialized]        int m_Number;     }

However, when .NET deserializes the object, it initializes each non-serializable member variable to the default value for that type (null for all reference types). It's then up to you to provide code to initialize the variables to their correct values. To that end, the object needs to know when it's being deserialized. The notification takes place by implementing the interface IDeserializationCallback, defined in the System.Runtime.Serialization namespace:

     public interface IDeserializationCallback     {        void OnDeserialization(object sender);     }

IDeserializationCallback's single method, OnDeserialization( ), is called after .NET has deserialized the object, allowing it to perform the required custom initialization steps. The sender parameter is ignored and is always set to null by .NET. Example 9-1 demonstrates how you can implement IDeserializationCallback. In the example, the class MyClass has a database connection as a member variable. The connection object (SqlConnection) isn't a serializable type and so is marked with the NonSerialized attribute. MyClass creates a new connection object in its implementation of OnDeserialization( ), because after deserialization the connection member is set to its default value of null. MyClass then initializes the connection object by providing it with a connection string and opens it.

Example 9-1. Deserialized event using IDeserializationCallback
 using System.Runtime.Serialization; [Serializable] public class MyClass : IDeserializationCallback {    [NonSerialized]    IDbConnection m_Connection;    string m_ConnectionString;    public void OnDeserialization(object sender)    {       Debug.Assert(m_Connection == null);       m_Connection = new SqlConnection(  );       m_Connection.ConnectionString = m_ConnectionString;       m_Connection.Open(  );    }    /* Other members */ }

You can't initialize class members marked with the readonly directive in OnDeserialization( )such members can only be initialized in a constructor. If you need to initialize read-only members, you have to use custom serialization, described later in this chapter.


On non-sealed classes, it is important to use either implicit interface implementation of IDeserializationCallback and have the base class mark its OnDeserialization( ) method as virtual to allow subclasses to override it, or to use the technique shown in Chapter 3 for explicit interface implementation by a class hierarchy. When class hierarchies are involved, you need to call your base-class implementation of OnDeserialization( ), and that requires a non-private implementation:

     [Serializable]     public class MyBaseClass : IDeserializationCallback     {        public virtual void OnDeserialization(object sender)        {...}     }     [Serializable]     public class MySubClass : MyBaseClass     {        public override void OnDeserialization(object sender)        {           //Perform custom steps, then:           base. OnDeserialization(sender);        }     }

9.1.2.1 Delegates and serialization

All delegate definitions are compiled into serializable classes. This means that when you serialize an object that has a delegate member variable, the internal invocation list of the delegate is serialized too. I believe that this renders delegates inherently non-serializable. There are no guarantees that the target objects in the internal list are serializable, so sometimes the serialization will work and sometimes it will throw a serialization exception. In addition, the object containing the delegate typically does not know or care about the actual state of the delegate. This is especially true when the delegate is used to manage event subscriptions, because the exact number and identities of the subscribers are often transient values that should not be persisted between application sessions.

You should mark delegate member variables as non-serializable, using the NonSerialized attribute:

     [Serializable]     public class MyClass     {        [NonSerialized]        EventHandler m_MyEvent;     }

In the case of events, you must also add the field attribute qualifier when applying the NonSerialized attribute:

     [Serializable]     public class MyPublisher     {        [field: NonSerialized]        public event EventHandler MyEvent;     }



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net