KGy SOFT

CustomSerializerSurrogateSelector Class

KGy SOFT Core Libraries Help
An ISurrogateSelector implementation that makes possible to serialize and deserialize any objects, including non-serializable ones by an IFormatter such as BinarySerializationFormatter or the legacy BinaryFormatter.
See the Remarks section for details and examples.
Inheritance Hierarchy

SystemObject
  KGySoft.Serialization.BinaryCustomSerializerSurrogateSelector

Namespace:  KGySoft.Serialization.Binary
Assembly:  KGySoft.CoreLibraries (in KGySoft.CoreLibraries.dll) Version: 5.0.0
Syntax

public sealed class CustomSerializerSurrogateSelector : ISurrogateSelector, 
	ISerializationSurrogate, IDisposable

The CustomSerializerSurrogateSelector type exposes the following members.

Constructors

  NameDescription
Public methodCustomSerializerSurrogateSelector
Initializes a new instance of the CustomSerializerSurrogateSelector class
Top
Properties

  NameDescription
Public propertyIgnoreISerializable
Gets or sets whether the ISerializable implementation should be ignored. The value of this property can be overridden by handling the Serializing event.
Default value: .
Public propertyIgnoreNonExistingFields
Gets or sets whether serialization data without a corresponding field should be silently ignored on deserialization. The field setting can be adjusted by handling the SettingField event.
Default value: .
Public propertyIgnoreNonSerializedAttribute
Gets or sets whether the fields that are marked by NonSerializedAttribute should be forcibly serialized. The value of this property can be overridden by handling the GettingField event.
Default value: .
Top
Methods

  NameDescription
Public methodChainSelector
Specifies the next ISurrogateSelector for surrogates to examine if the current instance does not have a surrogate for the specified type and assembly in the specified context.
Public methodDispose
Releases the resources held by this CustomSerializerSurrogateSelector instance.
Public methodEquals
Determines whether the specified object is equal to the current object.
(Inherited from Object.)
Public methodGetHashCode
Serves as the default hash function.
(Inherited from Object.)
Public methodGetNextSelector
Returns the next surrogate selector in the chain.
Public methodGetSurrogate
Finds the surrogate that represents the specified object's type, starting with the specified surrogate selector for the specified serialization context.
Public methodGetType
Gets the Type of the current instance.
(Inherited from Object.)
Public methodToString
Returns a string that represents the current object.
(Inherited from Object.)
Top
Events

  NameDescription
Public eventDeserializing
Occurs when an object is being deserialized.
If you initialize the Object manually make sure you set the Handled property to  to omit the default deserialization logic.
Public eventGettingField
Occurs when a field value is about to be retrieved on serialization. You can adjust the Name, Value and Type of the entry to be stored, or set the Handled property to  to prevent storing any value for the current Field. The Handled property might be initialized to  for fields that are marked by the NonSerializedAttribute.
Public eventObjectDataObtained
Occurs when the SerializationInfo of the object to be serialized has been obtained. You still can adjust its content before the actual serialization.
Public eventObjectDataRestored
Occurs when the SerializationInfo of the object to be deserialized has been processed.
Public eventSerializing
Occurs when an object is being serialized, before saving its inner content.
If you populate the SerializationInfo manually make sure you set the Handled property to  to omit the default serialization logic.
Public eventSettingField
Occurs when a field value is about to be set on deserialization. You can adjust the associated Field and its desired Value to be set or you can set the Handled property to  to prevent setting any field by the default logic.
Top
Extension Methods

  NameDescription
Public Extension MethodConvert(Type, CultureInfo)Overloaded.
Converts an Object specified in the obj parameter to the desired targetType.
See the Examples section of the generic ConvertTTarget(Object, CultureInfo) overload for an example.
(Defined by ObjectExtensions.)
Public Extension MethodCode exampleConvertTTarget(CultureInfo)Overloaded.
Converts an Object specified in the obj parameter to the desired TTarget.
(Defined by ObjectExtensions.)
Public Extension MethodIn (Defined by ObjectExtensions.)
Public Extension MethodTryConvert(Type, Object)Overloaded.
Tries to convert an Object specified in the obj parameter to the desired targetType.
(Defined by ObjectExtensions.)
Public Extension MethodTryConvert(Type, CultureInfo, Object)Overloaded.
Tries to convert an Object specified in the obj parameter to the desired targetType.
(Defined by ObjectExtensions.)
Public Extension MethodTryConvertTTarget(TTarget)Overloaded.
Tries to convert an Object specified in the obj parameter to the desired TTarget.
See the Examples section of the ConvertTTarget(Object, CultureInfo) method for a related example.
(Defined by ObjectExtensions.)
Public Extension MethodTryConvertTTarget(CultureInfo, TTarget)Overloaded.
Tries to convert an Object specified in the obj parameter to the desired TTarget.
See the Examples section of the ConvertTTarget(Object, CultureInfo) method for a related example.
(Defined by ObjectExtensions.)
Top
Remarks

By using the CustomSerializerSurrogateSelector you can serialize and deserialize any types.

Note Note
The BinarySerializationFormatter is also able to serialize non-serializable types by itself by using the RecursiveSerializationAsFallback option. But the CustomSerializerSurrogateSelector provides more control and can be used also for other formatters.

To serialize a non-serializable type, or a type, which contains non-serializable types, it is usually enough to assign a CustomSerializerSurrogateSelector to the formatter:

C#
var formatter = new BinaryFormatter { SurrogateSelector = new CustomSerializerSurrogateSelector() };
formatter.Serialize(stream, myObject);

Solving compatibility issues between different platforms

Note Note
Some types that are serializable in .NET Framework are not serializable in .NET Core/.NET Standard. This class can be a solution also for this problem. However, the solution is not always as simple as assigning a CustomSerializerSurrogateSelector instance to the SurrogateSelector property. See some cases and their solution in the list below.

Basic case
If the only difference is that the type, which is serializable in the original platform is not marked by the SerializableAttribute in the new one (eg.: MemoryStream is not serializable in .NET Core but otherwise it is still the same as in .NET Framework), then it is enough to use the default CustomSerializerSurrogateSelector:
C#
// deserializing a MemoryStream in .NET Core that was serialized in .NET Framework
var formatter = new BinaryFormatter { SurrogateSelector = new CustomSerializerSurrogateSelector() };
var memoryStream = (MemoryStream)formatter.Deserialize(streamSerializedInNetFramework);
The original type implements ISerializable
In .NET Core there are several types that still implement ISerializable, though their ISerializable.GetObjectData method throws a PlatformNotSupportedException (eg. DBNull, Type and Assembly in .NET Core 2.0). On the other hand, in .NET Core 3.0 DBNull is serializable again, Assembly remained the same and from Type the ISerializable implementation has also been removed along with the SerializableAttribute. For such cases there are more possible solutions:
  • In best cases it is enough to use the BinarySerializationFormatter both for serializing and deserializing without any surrogate selector. It natively supports Type instances in all platforms.
  • When serializing by CustomSerializerSurrogateSelector you can set the IgnoreISerializable property to force serializing the object by fields. If the fields are the same on the deserializing side, then the object will be deserializable by using the CustomSerializerSurrogateSelector on both sides with the same settings.
  • If fields have also changed you can customize the serializing and deserializing process by handling the events (see below).
Providing backwards compatibility
The Basic case might not work the other way around: serializing an object in .NET Core might not be deserializable in .NET Framework, for example. It can be avoided if:
Deserializing refactored types
If the object was serialized by fields but the field names have been refactored since then, then you can handle the SettingField event where you can either lookup and set the appropriate Field or do whatever custom processing and set the Handled property to  to indicate that you processed the entry manually. If you can initialize the complete object by yourself based on the serialized SerializationInfo, then you can handle the Deserializing event and set the Handled property to .
C#
// deserializing a type, whose fields used to have "m_" prefix, which have been removed
var surrogate = new CustomSerializerSurrogateSelector();
surrogate.Deserializing += (sender, args) =>
{
    // manipulating data only if the current class is the one that changed
    if (!(args.Object is MyChangedClass))
        return;

    // Replacing serialization entry names using the ReplaceValue extension method.
    // An alternative solution would be initializing args.Object by ourselves and
    // setting the args.Handled to true to prevent default deserialization logic.
    foreach (SerializationEntry entry in args.SerializationInfo)
    {
        if (entry.Name.StartsWith("m_"))
            args.SerializationInfo.ReplaceValue(entry.Name, 
                entry.Name.Substring(2), entry.Value, entry.ObjectType);
    }
};

var formatter = new BinaryFormatter // or a BinarySerializationFormatter
{
    SurrogateSelector = surrogate, // to remap field names as specified above
    Binder = new WeakAssemblySerializationBinder() // if assembly version changed, too
};

return (MyChangedClass)formatter.Deserialize(streamContainingOldData);
Tip Tip
If the name of the type changed too, you can use the CustomSerializationBinder class.

Tip Tip
Some of the solutions above are more workarounds for situations arose rather than recommended practices. If it is known that a type will be deserialized in another environment and it can be completely restored by its public members, then a text-based serialization (see also XmlSerializer) can be a better choice.
See Also

Reference