Binary serialization

In XmlSerialization, the output of the serialization is an XML file that can be easily opened by Notepad. However, as explained previously, creating a file adds to the overall storage space required by the application, which may not be desirable in all circumstances. 

We also observed that if we marked any attribute with the access modifier of private, it was not copied across to the generated XML file. This may also be an issue in many cases. 

In this section, we will look at an alternative approach in which we will serialize the data to a stream of bytes. This data will not be viewable like the XML file but will save us space and will deal with private attributes in a much better way. 

.NET Framework provides us with the System.Runtime.Serialization and System.Runtime.Serialization.Formatters.Binary namespaces, which provide us with helper classes for dealing with binary serialization. 

To understand how binary serialization works, let's look at the following example. We will work with Student, a similar class to what we created while we were working on XML serialization:

[Serializable]
public class StudentBinary
{
public string FirstName;
public string LastName;
public int ID;
public string Feedback;

public StudentBinary(string firstName, string lastName, int Id, string feedback)
{
this.FirstName = firstName;
this.LastName = lastName;
this.ID = Id;
this.Feedback = feedback;
}
}

Please note that in the class declaration, just like in XmlSerialization, we used a Serializable tag in the declaration of the StudentBinary class. This indicates to the compiler that the StudentBinary class can be serialized.

We can use the following code to serialize and deserialize an object of this class:

StudentBinary stu = new StudentBinary("Jacob", "Almeida", 78, "Passed");            
IFormatter formatter = new BinaryFormatter();
using (Stream stream = new FileStream("StudentBinaryData.bin", FileMode.Create))
{
formatter.Serialize(stream, stu);
}

using (Stream stream = new FileStream("StudentBinaryData.bin", FileMode.Open))
{
StudentBinary studeseria = (StudentBinary)formatter.Deserialize(stream);
}

In the code example, we created an object of the StudentBinary class and then used a helper class, BinaryFormatter, to serialize the data into binary data. Once the data is serialized, using the FileStream helper class, we saved this binary data in a binary file, StudentBinaryData.bin.

In the next step, we open the file we created in the previous step and deserialize it back to the StudentBinary class. If we try to debug the application and do a quick watch on the studeseria variable, we will see the following output:

Now let's make one change to the preceding class: let's mark the LastName attribute as private. When we used XmlSerialization, we saw that any attribute marked with the private access modifier was excluded from the attribute. Let's do the same with binary serialization and observe the difference:

[Serializable]
public class StudentBinary
{
public string FirstName;
private string LastName;
public int ID;
public string Feedback;
public StudentBinary(string firstName, string lastName, int Id, string feedback)
{
this.FirstName = firstName;
this.LastName = lastName;
this.ID = Id;
this.Feedback = feedback;
}
}

If we now try to debug an application and do a QuickWatch on the studeseria variable, we will get the following output:

Note that even though we made LastName as private, it has no impact on the output. This illustrates the advantage binary serialization has on XmlSerialization

Just with XmlSerialization, we can also set tags on an attribute that would ensure that that attribute is ignored during the serialization. We can do it with the NonSerialized tag. In the following code implementation, we are using this tag for the Feedback attribute:

[Serializable]
public class StudentBinary
{
public string FirstName;
private string LastName;
public int ID;
[NonSerialized]
public string Feedback;

public StudentBinary(string firstName, string lastName, int Id, string feedback)
{
this.FirstName = firstName;
this.LastName = lastName;
this.ID = Id;
this.Feedback = feedback;
}
}

Even though binary serialization allows us to overcome the restriction around any attribute that is marked with the private access modifier, there are still some scenarios when we would deliberately want to restrict the exchange of certain data, especially those attributes that are sensitive and that we would like to restrict anyhow. We can do this by using the ISerializable interface.

In the following implemented code, we used a similar class, StudentBinaryInterface, and have implemented an ISerializable interface in it. As part of this interface, we must implement a GetObjectData method in this class. This method is classed when the class gets serialized. In this method, we will do encapsulation and not add any sensitive attributes to the serialized stream. Let's have a look at how to do this:

[Serializable]
public class StudentBinary:ISerializable
{
public string FirstName;
private string LastName;
public int ID;
public string Feedback;
protected StudentBinary(SerializationInfo info,
StreamingContext context)
{
FirstName = info.GetString("Value1");
Feedback = info.GetString("Value2");
ID = info.GetInt32("Value3");
}
public StudentBinary(string firstName, string lastName,
int Id, string feedback)
{
this.FirstName = firstName;
this.LastName = lastName;
this.ID = Id;
this.Feedback = feedback;
}
[System.Security.Permissions.SecurityPermission(
SecurityAction.Demand, SerializationFormatter = true)]
public void GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("Value1", FirstName);
info.AddValue("Value2", Feedback);
info.AddValue("Value3", ID);
}
}

In the preceding code example, we declared the LastName attribute as private. Through this code, we will try to exclude this attribute from being serialized. 

There are two important functions in this class:

  • GetObjectData: As illustrated previously, this function is called when the class gets serialized. In this method, we will serialize the data present in Firstname, Feedback, and ID. Note that LastName is not present in this. This has been to make sure that the data in the LastName attribute will not be added to the stream.
  • Constructor: As the class implements the ISerializable interface, it must implement the following constructor with the parameters of SerializationInfo and StreamingContext:
StudentBinary(SerializationInfo info, StreamingContext context)

This constructor will be called when the data will be deserialized into this class object. Note that in the constructor, we are accessing the values in the Value1, Value2, and Value3 attributes and converting them into their respective mapped attributes:

StudentBinary stu = new StudentBinary("Jacob", "Almeida", 78, "Passed");
IFormatter formatter = new BinaryFormatter();
using (Stream stream = new FileStream("StudentBinaryData.bin", FileMode.Create))
{
formatter.Serialize(stream, stu);
}
using (Stream stream = new FileStream("StudentBinaryData.bin", FileMode.Open))
{
StudentBinary studeseria = (StudentBinary)formatter.Deserialize(stream);
}

If we debug this code and do a QuickWatch on the studeseria variable, we get the following output:

Note that no value is present in the LastName attribute. In the next section, we will learn how to work with collections.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.159.82