Concepts to remember:

  • SerializableAttribute
  • Formatters:
    • BinaryFormatter
    • SoapFormatter
  • ISerializable
    • deserialization constructor
    • GetObjectData
  • Objects are reconstructed from inside out
  • IDeserializationCallback
    • OnDeserialization (this method is automatically called after the entire object graph has been deserialized)

 

 


using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;

namespace Serialization
{
class Program
{
static void Main(string[] args)
{
// Create subject of this experiment.
var toSerialize = new AccountDto()
{
Id=1000,
UserAssignedName = "Main account",
LastKnownBalance = 0
};

DemoBinaryFormatter( toSerialize );
DemoSoapFormatter( toSerialize );
var toSerialize2 = new AccountDto2()
{
Id = 1000,
UserAssignedName = "Main account",
LastKnownBalance = 0
};

DemoCustomSerialization( toSerialize2 );

toSerialize.LastKnownBalance = 1000000D; // yeah, you'd wish ...
DemoSerializationSurrogate( toSerialize );

}

/// <summary>
///
/// </summary>
/// <param name="toSerialize"></param>
private static void DemoSerializationSurrogate(AccountDto toSerialize)
{
Console.WriteLine("Serializing using soap formatter and a serialization surrogate");
// Serialize subject into a memory stream
var formatter = new SoapFormatter();
var selector = new SurrogateSelector();
selector.AddSurrogate(
typeof( AccountDto ),
new StreamingContext( StreamingContextStates.All ),
new AccountDtoSerializationSurrogate() );
formatter.SurrogateSelector = selector;
var stream = new MemoryStream();
formatter.Serialize(stream, toSerialize);

// Dump stream.
stream.Position = 0;
var content = new StreamReader(stream).ReadToEnd();
Console.WriteLine($"Object serialized as SOAP follows:{Environment.NewLine}{content}");

// Deserialize. Remember to reset stream position.
stream.Position = 0;
var deserializedObject = formatter.Deserialize(stream);

// Compare source and result, they should match field by field.
var match = toSerialize.Equals(deserializedObject);
Console.WriteLine($"Source and deserialized objects match?: {match}");

}

/// <summary>
///
/// </summary>
/// <param name="toSerialize"></param>
private static void DemoCustomSerialization( AccountDto2 toSerialize )
{
Console.WriteLine("Serializing using soap formatter on an ISerializable object");

// Serialize subject into a memory stream
var formatter = new SoapFormatter();
var stream = new MemoryStream();
formatter.Serialize(stream, toSerialize);

// Dump stream.
stream.Position = 0;
var content = new StreamReader(stream).ReadToEnd();
Console.WriteLine($"Object serialized as SOAP follows:{Environment.NewLine}{content}");

// Deserialize. Remember to reset stream position.
stream.Position = 0;
var deserializedObject = formatter.Deserialize(stream);

// Compare source and result, they should match field by field.
var match = toSerialize.Equals(deserializedObject);
Console.WriteLine($"Source and deserialized objects match?: {match}");
}

/// <summary>
///
/// </summary>
/// <param name="toSerialize"></param>
private static void DemoSoapFormatter( AccountDto toSerialize )
{
Console.WriteLine("Serializing using soap formatter");

// Serialize subject into a memory stream
var formatter = new SoapFormatter();
var stream = new MemoryStream();
formatter.Serialize(stream, toSerialize);

// Dump stream.
stream.Position = 0;
var content = new StreamReader( stream ).ReadToEnd();
Console.WriteLine($"Object serialized as SOAP follows:{Environment.NewLine}{content}");

// Deserialize. Remember to reset stream position.
stream.Position = 0;
var deserializedObject = formatter.Deserialize(stream);

// Compare source and result, they should match field by field.
var match = toSerialize.Equals(deserializedObject);
Console.WriteLine($"Source and deserialized objects match?: {match}");

}

/// <summary>
///
/// </summary>
private static void DemoBinaryFormatter( AccountDto toSerialize )
{
Console.WriteLine( "Serializing using binary formatter");

// Serialize subject into a memory stream
var formatter = new BinaryFormatter();
var stream = new MemoryStream();
formatter.Serialize(stream, toSerialize);

// Deserialize. Remember to reset stream position.
stream.Position = 0;
var deserializedObject = formatter.Deserialize(stream);

// Compare source and result, they should match field by field.
var match = toSerialize.Equals(deserializedObject);
Console.WriteLine($"Source and deserialized objects match?: {match}");
}
}

/// <summary>
///
/// </summary>
internal class AccountDtoSerializationSurrogate : ISerializationSurrogate
{
#region Implementation of ISerializationSurrogate

/// <summary>
/// Populates the provided <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the object.
/// </summary>
/// <param name="obj">The object to serialize. </param><param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data. </param><param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization. </param><exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
public void GetObjectData( object obj, SerializationInfo info, StreamingContext context )
{
var source = obj as AccountDto;
if( source == null )
{
throw new InvalidOperationException(
$"GetObjectData expected an AccountDto, received a {obj.GetType().Name}" );
}

info.AddValue( AccountDto.Id_PSN, source.Id );
info.AddValue( AccountDto.UAN_PSN, source.UserAssignedName );
info.AddValue( AccountDto.LKB_PSN, source.LastKnownBalance );
}

/// <summary>
/// Populates the object using the information in the <see cref="T:System.Runtime.Serialization.SerializationInfo"/>.
/// </summary>
/// <returns>
/// The populated deserialized object.
/// </returns>
/// <param name="obj">The object to populate. </param><param name="info">The information to populate the object. </param><param name="context">The source from which the object is deserialized. </param><param name="selector">The surrogate selector where the search for a compatible surrogate begins. </param><exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
public object SetObjectData( object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector )
{
var target = obj as AccountDto;
if( target == null)
{
throw new InvalidOperationException(
$"SetObjectData expected an AccountDto, received a {obj.GetType().Name}");
}

target.Id = info.GetInt32( AccountDto.Id_PSN );
target.UserAssignedName = info.GetString( AccountDto.UAN_PSN );
target.LastKnownBalance = info.GetDouble( AccountDto.LKB_PSN );

return target; // Not clear, should return null or the newly populated object?
}

#endregion
}

[Serializable]
class AccountDto2: ISerializable
{
#region Properties
public int Id { get; set; }
public string UserAssignedName { get; set; }
public double LastKnownBalance { get; set; }
#endregion

#region Lifetime management
/// <summary>
/// Constructor is empty by design.
/// </summary>
public AccountDto2()
{

}
#endregion

#region Overrides of Object

/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <returns>
/// true if the specified object is equal to the current object; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current object. </param>
public override bool Equals(object obj)
{
var other = obj as AccountDto2;
if (other == null)
{
return false;
}

return
this.Id == other.Id
&& this.UserAssignedName == other.UserAssignedName
&& this.LastKnownBalance == other.LastKnownBalance;
}

#endregion
#region Custom serialization support
// PSN stands for "Property serialized name"
private const string Id_PSN = "Id_PSN";
private const string UAN_PSN = "UAN_PSN";
private const string LKB_PSN = "LKB_PSN";
protected AccountDto2( SerializationInfo source, StreamingContext ctx )
{
Id = source.GetInt32( Id_PSN );
UserAssignedName = source.GetString( UAN_PSN );
LastKnownBalance = source.GetDouble( LKB_PSN );
}

#region Implementation of ISerializable

/// <summary>
/// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the target object.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data. </param><param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization. </param><exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
public void GetObjectData( SerializationInfo info, StreamingContext context )
{
info.AddValue( Id_PSN, this.Id );
info.AddValue( UAN_PSN, this.UserAssignedName );
info.AddValue( LKB_PSN, LastKnownBalance );
}

#endregion
#endregion
}
[Serializable]
class AccountDto
{
#region Properties
public int Id { get; set; }
public string UserAssignedName { get; set; }
public double LastKnownBalance { get; set; }
#endregion

#region Serialization surrogate support
// PSN stands for "Property serialized name"
public const string Id_PSN = "Id_PSN";
public const string UAN_PSN = "UAN_PSN";
public const string LKB_PSN = "LKB_PSN";
#endregion

#region Lifetime management

public AccountDto()
{
Console.WriteLine( "Constructor for AccountDto has been called");
// Note this line gets written only once.
// That confirms that the constructors are not called during deserialization.
}
#endregion

#region Overrides of Object

/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <returns>
/// true if the specified object is equal to the current object; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current object. </param>
public override bool Equals( object obj )
{
var other = obj as AccountDto;
if( other == null )
{
return false;
}

return
this.Id == other.Id
&& this.UserAssignedName == other.UserAssignedName
&& this.LastKnownBalance == other.LastKnownBalance;
}

#endregion
}
}

Advertisements