When is a singleton not a singleton? Serialization!
Some days ago I wrote about the issue of having multiple instances of a singleton class in the same AppDomain. As for the implementation I gave and as emerged from the feedback to the post, this can happen when that class is serialized and deserialized or when it is instantiated via reflection. This is the original implementation:
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
// Private constructor to prevent external instantiation
private Singleton()
{ }
public static Singleton Instance
{
get { return instance; }
}
}
In case of serialization, there's actually a workaround explicitly provided by the .NET framework. This consists in implementing the ISerializable interface to provide a custom serialization mechanism.
Upon serialization, the GetObjectData method of the ISerializable interface is called to get the custom data needed for serializing the object. These data is set by setting up an appropriate SerializationInfo object. What's needed is a way to instruct the formatter about the identity of the object to be serialized, so that when it's deserialized it will be a reference to the same object - which we need to be unique. This can be accomplished by using the SetType method of the SerializationInfo object passed as a parameter to the GetObjectData method.
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(...); // accepts a Type parameter
}
What's the type we need to supply to the SetType method? It's not typeof(Singleton) - no need to specify that though, it's implicit - because in that case, upon deserialization, a particular constructor that the class has to define is called. The following is the signature of the constructor called by default when deserializing a class which implements the ISerializable interface:
Singleton(SerializationInfo, StreamingContext);
If no type is set using the SerializationInfo.SetType method into the GetObjectData method (called upon serialization), the above constructor is called on deserialization to populate the object being deserialized with custom data. If such a constructor is not implemented a SerializationException exception is thrown.
We won't need to implement that constructor since we want to avoid instantiating the class, but instead we need to pass to the SerializationInfo.SetType method the type of a helper class capable of returning the right instance of our singleton class. Again, the framework provides an interface explicitly designed for this task, called IObjectReference.
namespace System.Runtime.Serialization
{
public interface IObjectReference
{
object GetRealObject(StreamingContext context);
}
}
This interface lets you specify that the class implementing it doesn't create new objects, but instead returns a reference to another object. This is good for us, since we can implement this interface by returning, via the GetRealObject method, a reference to the singleton instance of the Singleton class, thus preventing the formatter from instantiating a new object during deserialization.
So our helper class ends up having an implementation like in the following code snippet:
[Serializable]
internal class SingletonSerializationHelper : IObjectReference
{
public object GetRealObject(StreamingContext context)
{
return Singleton.Instance;
}
}
Going back to the Singleton class, the original code now is edited to make use of the SingletonSerialization helper class:
[Serializable]
public class Singleton : ISerializable
{
private static readonly Singleton instance = new Singleton();
// Private constructor to prevent external instantiation
private Singleton()
{ }
public static Singleton Instance
{
get { return instance; }
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(typeof(SingletonSerializationHelper));
}
}
What this code does when an instance of the class is serialized and deserialized can be summarized as follows:
- Upon serialization, the GetObjectData method of the Singleton class is called and the formatter is instructed that the type of the object being serialized is actually not Singleton, but instead SingletonSerializationHelper.
- Upon deserialization, the formatter knows that it needs to instantiate an object of type SingletonSerializationHelper, this being a class implementing the IObjectReference interface. Therefore, instead of calling its constructor, the GetRealObjectMethod is called, and the right, singleton instance of the Singleton class is returned.
Again, note that if the SerializationInfo.SetType method wasn't called during serialization, when deserializing the formatter would have tried to call the overloaded constructor of the Singleton class, and if it wasn't provided a SerializationException exception would have been thrown. The reason why it isn't called upon deserialization is that we make the formatter believe that the type of the class it will have to deserialize is of type SingletonSerializationHelper in place of Singleton.
This example appears on the MSDN Library in a couple of pages about serialization, although I learned about this workaround when reading an old article written by Jeffrey Ritcher on the MSDN Magazine.