Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Controlling the Serialized/Deserialized Data

As discussed earlier in this chapter, the best way to get control over the serialization and deserializa- tion process is to use the OnSerializing, OnSerialized, OnDeserializing, OnDeserialized, NonSerialized, and OptionalField attributes. However, there are some very rare scenarios where these attributes do not give you all the control you need. In addition, the formatters use reflection internally and reflection is slow, which increases the time it takes to serialize and deserialize objects. To get complete control over what data is serialized/deserialized or to eliminate the use of reflection,


your type can implement the System.Runtime.Serialization.ISerializable interface, which

is defined as follows.

 

public interface ISerializable {

void GetObjectData(SerializationInfo info, StreamingContext context);

}

 

This interface has just one method in it, GetObjectData. But most types that implement this interface will also implement a special constructor that I’ll describe shortly.

       
   
 
 

 

 

When a formatter serializes an object graph, it looks at each object. If its type implements the ISerializable interface, then the formatter ignores all custom attributes and instead constructs a new System.Runtime.Serialization.SerializationInfo object. This object contains the actual set of values that should be serialized for the object.

When constructing a SerializationInfo, the formatter passes two parameters: Type and System.Runtime.Serialization.IFormatterConverter. The Type parameter identifies the object that is being serialized. Two pieces of information are required to uniquely identify a type: the string name of the type and its assembly’s identity (which includes the assembly name, version, culture, and public key). When a SerializationInfo object is constructed, it obtains the type’s full name (by internally querying Type’s FullName property) and stores this string in a private field. You can obtain the type’s full name by querying SerializationInfo’s FullTypeName property. Likewise, the constructor obtains the type’s defining assembly (by internally querying Type’s Module property followed by querying Module’s Assembly property followed by querying Assembly’s FullName property) and stores this string in a private field. You can obtain the assem- bly’s identity by querying SerializationInfo’s AssemblyName property.


 

After the SerializationInfo object is constructed and initialized, the formatter calls the type’s GetObjectData method, passing it the reference to the SerializationInfo object. The Get­ ObjectData method is responsible for determining what information is necessary to serialize the object and adding this information to the SerializationInfo object. GetObjectData indicates what information to serialize by calling one of the many overloaded AddValue methods provided by the SerializationInfo type. AddValue is called once for each piece of data that you want to add.



The following code shows an approximation of how the Dictionary<TKey, TValue> type imple- ments the ISerializable and IDeserializationCallback interfaces to take control over the serialization and deserialization of its objects.

 

[Serializable]

public class Dictionary<TKey, TValue>: ISerializable, IDeserializationCallback {

// Private fields go here (not shown)

 

private SerializationInfo m_siInfo; // Only used for deserialization

 

// Special constructor (required by ISerializable) to control deserialization [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] protected Dictionary(SerializationInfo info, StreamingContext context) {

// During deserialization, save the SerializationInfo for OnDeserialization m_siInfo = info;

}

 

// Method to control serialization [SecurityCritical]

public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {

 

info.AddValue("Version", m_version);

info.AddValue("Comparer", m_comparer, typeof(IEqualityComparer<TKey>)); info.AddValue("HashSize", (m_ buckets == null) ? 0 : m_buckets.Length); if (m_buckets != null) {

KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[Count]; CopyTo(array, 0);

info.AddValue("KeyValuePairs", array, typeof(KeyValuePair<TKey, TValue>[]));

}

}


// Method called after all key/value objects have been deserialized

public virtual void IDeserializationCallback.OnDeserialization(Object sender) { if (m_siInfo == null) return; // Never set, return

 

Int32 num = m_siInfo.GetInt32("Version"); Int32 num2 = m_siInfo.GetInt32("HashSize"); m_comparer = (IEqualityComparer<TKey>)

m_siInfo.GetValue("Comparer", typeof(IEqualityComparer<TKey>)); if (num2 != 0) {

m_buckets = new Int32[num2];

for (Int32 i = 0; i < m_buckets.Length; i++) m_buckets[i] = ­1; m_entries = new Entry<TKey, TValue>[num2];

m_freeList = ­1;

KeyValuePair<TKey, TValue>[] pairArray = (KeyValuePair<TKey, TValue>[]) m_siInfo.GetValue("KeyValuePairs", typeof(KeyValuePair<TKey, TValue>[]));

if (pairArray == null) ThrowHelper.ThrowSerializationException(

ExceptionResource.Serialization_MissingKeys);

 

for (Int32 j = 0; j < pairArray.Length; j++) { if (pairArray[j].Key == null)

ThrowHelper.ThrowSerializationException( ExceptionResource.Serialization_NullKey);

 

Insert(pairArray[j].Key, pairArray[j].Value, true);

}

} else { m_buckets = null; } m_version = num;

m_siInfo = null;

}

 

Each AddValue method takes a String name and some data. Usually, the data is of a simple value type like Boolean, Char, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal, or DateTime. However, you can also call AddValue, passing it a reference to an Object such as a String. After GetObjectData has added all of the necessary serialization informa- tion, it returns to the formatter.

       
   
 
 


The formatter now takes all of the values added to the SerializationInfo object and serial- izes each of them out to the stream. You’ll notice that the GetObjectData method is passed another parameter: a reference to a System.Runtime.Serialization.StreamingContext object. Most types’ GetObjectData methods will completely ignore this parameter, so I will not discuss it now.

Instead, I’ll discuss it in the “Streaming Contexts” section later in this chapter.

 

So now you know how to set all of the information used for serialization. At this point, let’s turn our attention to deserialization. As the formatter extracts an object from the stream, it allocates memory for the new object (by calling the System.Runtime.Serialization.FormatterServices type’s static GetUninitializedObject method). Initially, all of this object’s fields are set to 0 or null. Then, the formatter checks if the type implements the ISerializable interface. If this interface exists, the formatter attempts to call a special constructor whose parameters are identical to that of the Get­ ObjectData method.

If your class is sealed, then it is highly recommended that you declare this special constructor to be private. This will prevent any code from accidentally calling increasing security. If not, then you should declare this special constructor as protected so that only derived classes can call it. Note that the formatters are able to call this special constructor no matter how it is declared.

This constructor receives a reference to a SerializationInfo object containing all of the values added to it when the object was serialized. The special constructor can call any of the GetBoolean, GetChar, GetByte, GetSByte, GetInt16, GetUInt16, GetInt32, GetUInt32, GetInt64, Get­ UInt64, GetSingle, GetDouble, GetDecimal, GetDateTime, GetString, and GetValue methods, passing in a string corresponding to the name used to serialize a value. The value returned from each of these methods is then used to initialize the fields of the new object.

When deserializing an object’s fields, you should call the Get method that matches the type of value that was passed to the AddValue method when the object was serialized. In other words, if the GetObjectData method called AddValue, passing it an Int32 value, then the GetInt32 method should be called for the same value when deserializing the object. If the value’s type in the stream doesn’t match the type you’re trying to get, then the formatter will attempt to use an IFormatter­ Convert object to “cast” the stream’s value to the desired type.

As I mentioned earlier, when a SerializationInfo object is constructed, it is passed an object whose type implements the IFormatterConverter interface. Because the formatter is responsible for constructing the SerializationInfo object, it chooses whatever IFormatterConverter type it wants. Microsoft’s BinaryFormatter and SoapFormatter types always construct an instance of the System.Runtime.Serialization.FormatterConverter type. Microsoft’s formatters don’t offer any way for you to select a different IFormatterConverter type.

The FormatterConverter type calls the System.Convert class’s static methods to convert values between the core types, such as converting an Int32 to an Int64. However, to convert a value between other arbitrary types, the FormatterConverter calls Convert’s ChangeType method to


cast the serialized (or original) type to an IConvertible interface and then calls the appropriate interface method. Therefore, to allow objects of a serializable type to be deserialized as a different type, you may want to consider having your type implement the IConvertible interface. Note that the FormatterConverter object is used only when deserializing objects and when you’re calling a Get method whose type doesn’t match the type of the value in the stream.

Instead of calling the various Get methods previously listed, the special constructor could in- stead call GetEnumerator, which returns a System.Runtime.Serialization.Serialization­ InfoEnumerator object that can be used to iterate through all the values contained within the SerializationInfo object. Each value enumerated is a System.Runtime.Serialization.

SerializationEntry object.

 

Of course, you are welcome to define a type of your own that derives from a type that imple- ments ISerializable’s GetObjectData and special constructor. If your type also implements ISerializable, then your implementation of GetObjectData and your implementation of the special constructor must call the same functions in the base class in order for the object to be serial- ized and deserialized properly. Do not forget to do this or the objects will not serialize or deserialize correctly. The next section explains how to properly define an ISerializable type whose base type doesn’t implement this interface.

If your derived type doesn’t have any additional fields in it and therefore has no special serial- ization/deserialization needs, then you do not have to implement ISerializable at all. Like all interface members, GetObjectData is virtual and will be called to properly serialize the object. In addition, the formatter treats the special constructor as “virtualized.” That is, during deserialization, the formatter will check the type that it is trying to instantiate. If that type doesn’t offer the special constructor, then the formatter will scan base classes until it finds one that implements the special constructor.

       
   
 
 



Date: 2016-03-03; view: 800


<== previous page | next page ==>
Nbsp;   How Formatters Serialize Type Instances | How to Define a Type That ImplementsISerializable When the Base Type Doesn’t Implement This Interface
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.008 sec.)