Custom Serialization in C#
Introduction
Serialization is the process of converting an object into a form that can be easily transported. In C#, this often means converting an object into a binary or XML format. Deserialization is the process of converting the serialized form back into an object. Custom serialization allows developers to control the serialization and deserialization processes, providing flexibility for handling special cases.
Why Custom Serialization?
Default serialization provided by .NET may not always meet specific requirements, such as:
- Ignoring certain fields or properties
- Customizing the format of serialized data
- Encrypting data before serialization
- Handling complex object graphs
Implementing Custom Serialization
In C#, custom serialization can be implemented by using the ISerializable
interface. This interface requires the GetObjectData
method to be implemented, which is responsible for populating a SerializationInfo
object with the data needed to serialize the object.
Example: Custom Serialization
The following example demonstrates how to implement custom serialization for a class named Person
.
using System;
using System.Runtime.Serialization;
[Serializable]
public class Person : ISerializable
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public Person() { }
protected Person(SerializationInfo info, StreamingContext context)
{
FirstName = info.GetString("FirstName");
LastName = info.GetString("LastName");
Age = info.GetInt32("Age");
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("FirstName", FirstName);
info.AddValue("LastName", LastName);
info.AddValue("Age", Age);
}
public override string ToString()
{
return $"{FirstName} {LastName}, Age: {Age}";
}
}
class Program
{
static void Main()
{
Person person = new Person { FirstName = "John", LastName = "Doe", Age = 30 };
// Serialize
IFormatter formatter = new BinaryFormatter();
using (Stream stream = new FileStream("person.bin", FileMode.Create, FileAccess.Write))
{
formatter.Serialize(stream, person);
}
// Deserialize
using (Stream stream = new FileStream("person.bin", FileMode.Open, FileAccess.Read))
{
Person deserializedPerson = (Person)formatter.Deserialize(stream);
Console.WriteLine(deserializedPerson);
}
}
}
Explanation
Let's break down the example:
- The
Person
class implements theISerializable
interface. - The constructor
Person(SerializationInfo info, StreamingContext context)
is used during deserialization to reconstruct the object. - The
GetObjectData
method populates theSerializationInfo
object with the data needed for serialization. - In the
Main
method, thePerson
object is serialized to a file namedperson.bin
. - The serialized file is then deserialized back into a
Person
object and printed to the console.
Advanced Custom Serialization
For more advanced scenarios, you may need to handle additional serialization logic, such as encrypting data or managing versioning. Here's an example that demonstrates encrypting a property before serialization:
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;
[Serializable]
public class SecurePerson : ISerializable
{
public string FirstName { get; set; }
public string LastName { get; set; }
private string EncryptedData { get; set; }
public SecurePerson() { }
protected SecurePerson(SerializationInfo info, StreamingContext context)
{
FirstName = info.GetString("FirstName");
LastName = info.GetString("LastName");
EncryptedData = info.GetString("EncryptedData");
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("FirstName", FirstName);
info.AddValue("LastName", LastName);
// Encrypt the Age property
string ageData = "Age=" + 30; // Example data to encrypt
EncryptedData = Encrypt(ageData);
info.AddValue("EncryptedData", EncryptedData);
}
private string Encrypt(string data)
{
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
using (Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes("A1234567890A1234567890A123456789");
aes.IV = Encoding.UTF8.GetBytes("A1234567890A1234");
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
{
byte[] encryptedBytes = encryptor.TransformFinalBlock(dataBytes, 0, dataBytes.Length);
return Convert.ToBase64String(encryptedBytes);
}
}
}
public override string ToString()
{
return $"{FirstName} {LastName}, EncryptedData: {EncryptedData}";
}
}
class Program
{
static void Main()
{
SecurePerson person = new SecurePerson { FirstName = "Jane", LastName = "Doe" };
// Serialize
IFormatter formatter = new BinaryFormatter();
using (Stream stream = new FileStream("secureperson.bin", FileMode.Create, FileAccess.Write))
{
formatter.Serialize(stream, person);
}
// Deserialize
using (Stream stream = new FileStream("secureperson.bin", FileMode.Open, FileAccess.Read))
{
SecurePerson deserializedPerson = (SecurePerson)formatter.Deserialize(stream);
Console.WriteLine(deserializedPerson);
}
}
}
Conclusion
Custom serialization in C# provides a powerful mechanism to control how objects are serialized and deserialized. By implementing the ISerializable
interface, you can handle special requirements such as ignoring fields, customizing data formats, encrypting data, and more. This tutorial covered the basics and provided examples to help you get started with custom serialization in C#.