[It168 programming and development] What is serialization?
The. NET runtime environment is used to support user-defined streaming mechanisms. It stores the status of the object instance to the stored media. In this process, the public and private fields of the object and the name of the class (includingProgramSet), and then write the byte stream into the data stream. When the object is deserialized, a copy identical to the original object will be created.
Serialization objective:
1. Make the custom object persistent in a certain storage form;
2. transfer an object from one place to another.
Essentially, the serialization mechanism is to convert the class value into a general (that is, continuous) byte stream, and then the stream can be written to a disk file or any other streaming target. To write this stream, you must use the serialize and deserialize methods in the classes that implement the iformatter interface.
The. NET Framework provides the following two classes:
I. binaryformatter
Binaryformatter uses a binary Formatter for serialization. You only need to create an instance of the stream and formatting program to be used, and then call the serialize method of the formatting program. The stream and the object instance to be serialized are provided to this call as parameters. All member variables (or even variables marked as private) in the class will be serialized.
First, create a class:
[Serializable]
Public class myobject {
Public int n1 = 0;
Public int n2 = 0;
Public String STR = NULL;
}
The serializable attribute explicitly indicates that the class can be serialized. Similarly, we can use the nonserializable attribute to explicitly indicate that the class cannot be serialized.
Then we create an instance of this class, serialize it, and save it to the file for persistence:
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 ();
It is also very easy to restore an object to its previous state. First, create a formatter and a stream for reading, and then let the formatter deserialize the object.
Iformatter formatter = new binaryformatter ();
Stream stream = new filestream ("myfile. bin", filemode. Open,
Fileaccess. Read, fileshare. Read );
Myobject OBJ = (myobject) formatter. deserialize (fromstream );
Stream. Close ();
// The following is proof
Console. writeline ("N1: {0}", obj. N1 );
Console. writeline ("N2: {0}", obj. N2 );
Console. writeline ("str: {0}", obj. Str );
Ii. soapformatter
Previously, we used binaryformatter to serialize data in binary format. We can easily change the previous example to soapformatter, which will be formatted in XML, so it can be more portable. All you need to do isCodeThe formatting program in is changed to soapformatter, while the serialize and deserialize calls remain unchanged. For the example used above, the formatter will generate 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>
Note that the serializable attribute cannot be inherited. If a new class is derived from myobject, the new class must also be marked with this attribute; otherwise, serialization will fail. For example, if you try to serialize the following class instances, a serializationexception is displayed, indicating that the mystuff type is not marked as serializable.
Public class mystuff: myobject
{
Public int N3;
}
However, there is another question about the formatter. Suppose we only need XML, but we do not need the additional information unique to soap. What should we do? There are two solutions: 1. Compile a class that implements the iformatter interface. The method is similar to soapformatter, but there is no information you don't need. 2. Use the xmlserializer class provided by the framework.
The differences between the xmlserializer class and the first two mainstream serialization classes are:
1. the serializable attribute is not required. The serializable and nonserializable attributes are ignored, but the xmlignore attributes are used, which is similar to the nonserializable attribute.
2. This class cannot securely access private members. Therefore, you must change Private Members to public members or provide appropriate public features.
3. The serialized class must have a default constructor.
Let's change the previous myobject class:
Public class myobject {
Public int N1;
Public String STR;
Public myobject (){}
Public myobject (N1, STR)
{
This. n1 = N1;
This. Str = STR;
}
Public override string tostring ()
{
Return string. Format ("{0 }:{ 1}", this. STR, this. N1 );
}
}
Now we use the xmlserializer class to serialize the modified myobject. Because the xmlserializer class constructor has a type parameter, the xmlserializer object is explicitly connected to the class represented by this type parameter. The xmlserializer class also has the serialize and deserialize methods:
Myobject OBJ = new myobject (12, "some string ...");
Xmlserializer formatter = new xmlserializer (typeof (myobject ));
Stream stream = new filestream ("myfile. xml", filemode. Create,
Fileaccess. Write, fileshare. None );
Formatter. serialize (stream, OBJ );
// The following is deserialization.
Stream. Seek (0, seekorigin. Begin)
Myobject obj_out = (myobject) formatter. deserialize (Stream)
Stream. Close ();
Console. writeline (obj_out );
This simple column can be expanded to take advantage of more xmlserializer functions, including using attributes to control XML tags, using XML schemas, and soap encoding.
Custom serialization
If you want to serialize the class but are not satisfied with the data stream organization method, you can customize the serialization process by implementing the iserializable interface on the object. This function is especially useful when the value of the member variable becomes invalid after deserialization, but you need to provide the value for the variable to recreate the complete state of the object. In addition to declaring the class as serializable, The iserializable interface must be implemented. The getobjectdata method and a special constructor must be implemented. This constructor must be used for deserializing objects. When the getobjectdata method is implemented, the most commonly called serializationinfo method is addvalue, which has an overloaded version for all standard types (INT, Char, and so on; the streamingcontext parameter describes the source and target of the given serialized stream, so that we can know whether to serialize objects to persistent storage or serialize them across processes or machines. In deserialization, we call a set of getxxx methods provided by serializationinfo. They execute a variety of addvalue overload version inverse operations for all standard data types. 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 calling getobjectdata during serialization, you need to fill in the serializationinfo object provided in the method call. You only need to add the variable to be serialized in the form of name/value pairs. The name can be any text. As long as the serialized data is sufficient to restore the object during the deserialization process, you can freely select the member variable added to serializationinfo. If the base object implements iserializable, the derived class should call the getobjectdata method of the base object.
It should be emphasized that, when adding iserializable to a class, getobjectdata and special constructors with specific prototypes must be implemented at the same time-what is important, the list of parameters of the constructor must be the same as that of getobjectdata. This constructor will use the formatter to deserialize data from the stream during deserialization, then, use this constructor to list objects. If getobjectdata is missing, the compiler sends a warning. However, the constructor cannot be implemented forcibly, so no warning is given 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 version control problems, the current design is better than the setobjectdata method. For example, if you define the setobjectdata method as part of an interface, this method must be a public method, so that you have to write code to prevent multiple calls to the setobjectdata method. As you can imagine, if an object is performing some operations, but a malicious application calls the setobjectdata method of this object, it will cause some potential troubles.
In the deserialization process, use the constructor provided for this purpose to pass serializationinfo to the class. During object deserialization, any visibility constraints on constructors are ignored. Therefore, classes can be marked as public, protected, internal, or private. A good way is to mark the constructor as protect if the class is not encapsulated. If the class has been encapsulated, it should be marked as private. To restore the object state, you only need to use the name used during serialization to retrieve the value of the variable from serializationinfo. If the base class implements iserializable, you should call the base class constructor so that the base object can restore its variables.
If a new class is derived from the class that implements iserializable, the constructor and the getobjectdata method must be implemented as long as the new class contains any variable to be serialized. The following code snippet shows how to use the myobject class shown above to complete this operation.
[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 constructed after deserialization.
Objects are completely re-built, but calling methods in the deserialization process may bring adverse side effects, because the called methods may reference object references that are not deserialized at the call fashion. If the deserialization class implements ideserializationcallback, The onserialization method is automatically called after the entire object chart is deserialized. All referenced sub-objects are completely restored. Some classes do not use the above event listeners, so it is difficult to deserialize them. The hash is a typical example. It is very easy to retrieve keyword/value pairs during deserialization. However, the classes derived from the hash cannot be deserialized, therefore, some problems may occur when these objects are added to the return list. Therefore, we recommend that you do not call methods on the hash table.