Object serialization in. NET

Source: Internet
Author: User
Tags constructor hash implement soap object serialization variables thread versions
Object


Brief introduction
Serialization refers to the process of storing the state of an object instance to storage media. In this procedure, the public and private fields of the object, as well as the name of the class, including the assembly in which the class resides, are converted to a byte stream, which is then written to the data flow. When the object is deserialized later, the exact same copy of the original object is created.

When implementing serialization mechanisms in an object-oriented environment, there must be some trade-off between ease of use and flexibility. As long as you have sufficient control over this process, you can automate the process to a large extent. For example, simple binary serialization does not meet your needs, or you need to determine which fields in your class need to be serialized for specific reasons. The following sections explore the reliable serialization mechanisms provided by the. NET framework and highlight some of the important features that allow you to customize the serialization process as needed.

Persistent storage
We often need to save the field value of an object to disk and retrieve this data at a later time. Although this can be done without serialization, this approach is often cumbersome and error prone, and becomes more complex when you need to track the hierarchy of objects. Imagine writing a large business application that contains a large number of objects, and programmers have to write code for each object to save fields and properties to disk and restore those fields and properties from disk. Serialization provides a quick and easy way to achieve this goal.

The common language Runtime (CLR) manages the distribution of objects in memory, and the. NET Framework provides an automatic serialization mechanism by using reflection. After the object is serialized, the name of the class, the Assembly, and all the data members of the class instance are written to the storage media. Objects typically use member variables to store references to other instances. Class is serialized, the serialization engine tracks all the serialized reference objects to ensure that the same object is not serialized multiple times. The serialization architecture provided by the. NET framework can automatically handle object charts and circular references correctly. The only requirement for an object graph is that all objects referenced by the object being serialized must be marked as Serializable (see Basic serialization). Otherwise, an exception occurs when the serializer attempts to serialize an Unlabeled object.

When a serialized class is deserialized, the class is re-created and the values of all data members are automatically restored.

Marshaling by value
object is valid only in the application domain where the object is created. Unless the object is derived from MarshalByRefObject or marked as Serializable, any attempt to pass an object as a parameter or return it as a result will fail. If the object is marked as Serializable, the object is automatically serialized, transferred from one application domain to another, and then deserialized to produce an exact copy of the object in the second application domain. This process is often called marshaling by value.

If the object is derived from MarshalByRefObject, then the object reference, rather than the object itself, is passed to another application domain from one application domain. You can also mark objects derived from MarshalByRefObject as Serializable. When this object is used remotely, a formatter that is responsible for serialization and preconfigured to SurrogateSelector will control the serialization process and replace all objects derived from MarshalByRefObject with an agent. If it is not preconfigured to SurrogateSelector, the serialization architecture complies with the following standard serialization rules (see the steps in the serialization process).

Basic serialization
The easiest way to make a class serializable is to tag it with the Serializable property, as follows:

[Serializable]
public class MyObject {
public int n1 = 0;
public int n2 = 0;
public String str = NULL;
}
The following code fragment shows how to serialize an instance of this class to a file:

MyObject obj = new MyObject ();
OBJ.N1 = 1;
Obj.n2 = 24;
Obj.str = "some strings";
IFormatter formatter = new BinaryFormatter ();
Stream stream = new FileStream ("Myfile.bin", FileMode.Create,
FileAccess.Write, Fileshare.none);
Formatter. Serialize (stream, obj);
Stream. Close ();
This example uses a binary formatter for serialization. You simply create an instance of the stream and formatter to use, and then invoke the Serialize method of the formatter. The stream and the object instance to serialize are supplied as arguments to this call. All member variables in a class (even variables marked private) are serialized, but this is not explicitly reflected in this example. At this point, binary serialization differs from an XML serializer that serializes only common fields.

It is also very easy to restore an object to its previous state. First, you create the formatter and stream for reading, and then let the formatter deserialize the object. The following code fragment shows how to do this.

IFormatter formatter = new BinaryFormatter ();
Stream stream = new FileStream ("Myfile.bin", FileMode.Open,
FileAccess.Read, FileShare.Read);
MyObject obj = (MyObject) formatter. Deserialize (FromStream);
Stream. Close ();

Here's the proof.
Console.WriteLine ("N1: {0}", obj.n1);
Console.WriteLine ("N2: {0}", obj.n2);
Console.WriteLine ("Str: {0}", OBJ.STR);
The BinaryFormatter used above are highly efficient and can generate very compact byte streams. All objects serialized with this formatter can also be used for deserialization, and this formatter is an ideal tool for serializing objects that are deserialized on the. NET platform. It should be noted that the constructor is not invoked when deserializing an object. Adding this constraint to deserialization is due to performance considerations. However, this violates some of the run-time conventions commonly used by object writers, so developers should ensure that this particular convention is considered when marking objects as serializable.

If the requirement is portable, use SoapFormatter. The change you want to make is simply to replace the formatter in the above code with SoapFormatter, while the Serialize and deserialize calls are unchanged. For the example used above, the formatter produces the following results.

<soap-env:envelope
Xmlns:xsi=http://www.w3.org/2001/xmlschema-instance
Xmlns:xsd= "Http://www.w3.org/2001/XMLSchema"
xmlns:soap-enc=http://schemas.xmlsoap.org/soap/encoding/
xmlns:soap-env=http://schemas.xmlsoap.org/soap/envelope/
soap-env:encodingstyle=
"http://schemas.microsoft.com/soap/encoding/clr/1.0
http://schemas.xmlsoap.org/soap/encoding/"
xmlns:a1= "Http://schemas.microsoft.com/clr/assem/ToFile" >

<SOAP-ENV:Body>
<a1:myobject id= "Ref-1" >
<n1>1</n1>
<n2>24</n2>
<str id= "ref-3" > Some strings </str>
</a1:MyObject>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
It is to be noted that the Serializable property cannot be inherited. If a new class is derived from MyObject, the new class must also be marked with that property, otherwise it cannot be serialized. For example, if you attempt to serialize the following class instance, a serializationexception is displayed stating that the MyStuff type is not marked as serializable.

public class Mystuff:myobject
{
public int N3;
}
Using serialization attributes is convenient, but it has some of these limitations. For information about when to mark a class for serialization (because the class cannot be serialized after it is compiled), refer to the instructions (see serialization rules below).

Selective serialization
Classes typically contain fields that should not be serialized. For example, suppose a class uses a member variable to store the thread ID. When this class is deserialized, the thread that corresponds to the ID stored when serializing this class may no longer run, so serializing the value is meaningless. You can prevent them from being serialized by using the NonSerialized property to mark member variables, as follows:

[Serializable]
public class MyObject
{
public int N1;
[nonserialized] public int n2;
Public String str;
}
Custom serialization
You can customize the serialization process by implementing the ISerializable interface on the object. This feature is especially useful when the value of a member variable is invalidated after deserialization, but it is necessary to provide a value for the variable to reconstruct the complete state of the object. To implement ISerializable, you need to implement the GetObjectData method and a special constructor that you use to deserialize the object. The following code example shows how to implement ISerializable on the MyObject class mentioned in the previous section.

[Serializable]
public class Myobject:iserializable
{
public int N1;
public int n2;
Public String str;

Public MyObject ()
{
}

Protected MyObject (SerializationInfo info, StreamingContext context)
{
N1 = info. GetInt32 ("I");
N2 = info. GetInt32 ("J");
str = info. GetString ("K");
}

public virtual void GetObjectData (SerializationInfo info,
StreamingContext context)
{
Info. AddValue ("I", N1);
Info. AddValue ("J", N2);
Info. AddValue ("K", str);
}
}
When you call GetObjectData during serialization, you need to populate the SerializationInfo object provided in the method call. Simply add the variable that will be serialized as a name/value pair. The name can be any text. As long as the serialized data is sufficient to restore the object during deserialization, you are free to choose a member variable to add to the SerializationInfo. If the base object implements ISerializable, the derived class should call the GetObjectData method of its base object.

It should be emphasized that when adding iserializable to a class, you need to implement both GetObjectData and special constructors. If GetObjectData is missing, the compiler will issue a warning. However, because constructors cannot be enforced, a warning is not issued when the constructor is missing. If you try to deserialize a class without a constructor, an exception will occur. In terms of eliminating potential security and versioning issues, the current design is superior to the setobjectdata approach. For example, if you define the SetObjectData method as part of an interface, this method must be a public method, which allows the user to write code to prevent multiple calls to the SetObjectData method. It can be imagined that if an object is performing some action, and a malicious application calls the SetObjectData method of the object, it can cause some potential trouble.

During deserialization, SerializationInfo is passed to the class using the constructor provided for this purpose. When an object is deserialized, any visibility constraints on the constructor are ignored, so you can mark the class as public, protected, internal, or private. A good idea is to mark the constructor as protect in case the class is not encapsulated. If the class is encapsulated, it should be marked private. To restore the state of an object, you retrieve the value of the variable from the SerializationInfo simply by using the name that is used for serialization. If the base class implements ISerializable, the constructor of the base class should be called so that the underlying object can restore its variables.

If you derive a new class from a class that implements ISerializable, you must implement both the constructor and the GetObjectData method as long as the new class contains any variables that need to be serialized. The following code fragment shows how to do this by using the MyObject class shown above.

[Serializable]
public class Objecttwo:myobject
{
public int num;

Public ObjectTwo (): Base ()
{
}

Protected ObjectTwo (SerializationInfo si, StreamingContext context):
Base (Si,context)
{
num = si. GetInt32 ("num");
}

public override void GetObjectData (SerializationInfo si,
StreamingContext context)
{
Base. GetObjectData (Si,context);
Si. AddValue ("num", num);
}
}
Remember to call the base class in the deserialization constructor, otherwise, the constructor on the base class will never be called, and the complete object cannot be built after deserialization.

The object is completely rebuilt, but invoking the method during the deserialization process can have undesirable side effects because the invoked method might refer to an object reference that was not deserialized at the time of the call. If the class being deserialized implements Ideserializationcallback, the Onserialization method is automatically invoked when the entire object diagram is deserialized. At this point, all child objects that are referenced are fully restored. A hash table is a typical example of classes that do not use the event listeners mentioned above and are difficult to deserialize. It is easy to retrieve keyword/value pairs during deserialization, but there are some problems when adding the objects back to the hash table because there is no guarantee that the classes derived from the Hashtable have been deserialized. Therefore, it is recommended that you do not call methods on a hash table at this time.

Steps in the serialization process
When you invoke the Serialize method on a formatter, object serialization follows these rules:

Check to see if the formatter has a proxy selector. If so, check whether the agent picker handles objects of the specified type. If the picker processes this object type, ISerializable.GetObjectData is invoked on the agent picker.
If you do not have a proxy picker or if you do not handle this type, you will check whether the object is marked with the Serializable property. If not marked, a serializationexception is raised.
If the object is properly tagged, the object is checked for ISerializable. If implemented, GetObjectData is invoked on the object.
If the object does not implement Serializable, the default serialization policy is used, and all fields that are not marked as NonSerialized are serialized.
Version control
The. NET framework supports versioning and execution side-by-hand, and if the interfaces of the classes remain consistent, all classes can work across versions. Because serialization involves member variables rather than interfaces, you should be cautious when adding member variables to or removing variables from a class that you want to serialize across versions. This is especially true for classes that do not implement ISerializable. Any change in the state of the current version (such as adding a member variable, changing a variable type, or changing a variable name) means that if an existing object of the same type is serialized using an earlier version, it cannot be successfully deserialized.

If the state of an object needs to change between different versions, the author of the class can have two choices:

Realize ISerializable. This allows you to accurately control the serialization and deserialization process, adding and interpreting the future state correctly during deserialization.
Use the NonSerialized property to mark an unimportant member variable. This option is only available if the expected class changes slightly between different versions. For example, after you add a new variable to a higher version of the class, you can mark the variable as nonserialized to ensure that the class is compatible with earlier versions.
Serialization rules
Because classes cannot be serialized after they are compiled, serialization should be considered when designing new classes. The issues to consider are: do you have to send this class across application domains? Do you want to use this class remotely? How will the user use this class? Maybe they'll derive a new class from my class that needs to be serialized. As long as this possibility is available, the class should be marked as serializable. It is a good idea to mark all classes as serializable, except for the following:

All classes will never cross an application domain. If a class does not require serialization but needs to cross the application domain, derive this class from MarshalByRefObject.
Class stores special pointers that apply only to their current instance. For example, if a class contains unmanaged memory or file handles, make sure that the fields are marked as NonSerialized or that the class is not serialized at all.
Some data members contain sensitive information. In this case, it is recommended that you implement ISerializable and only serialize the required fields.




Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.