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:

  1. 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.
  2. 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. 

kick it on DotNetKicks.com

Published 04 May 2007 03:37 AM by simoneb
Filed under:

Comments

# DotNetKicks.com said on 03 May, 2007 08:20 PM

You've been kicked (a good thing) - Trackback from DotNetKicks.com

# Jonathan Allen said on 06 May, 2007 03:02 AM
If you are even considering serialization, then you shouldn't be using a singleton. As soon as you call serialize a second time, you no longer have a single instance. And if you really want a to be sure only one instance exists, just create a Module.
# simoneb said on 06 May, 2007 04:43 AM

I wouldn't call "an instance" an object serialized to some sotrage medium, you can't interact with it unless you deserialize it.

What do you mean by a module?

# Paul said on 03 June, 2007 11:35 PM
The problem I have with this is that it stops the serialization altogether straight after it outputs the 'SingletonSerializationHelper' type into the file. Before I took this advice I had a working serialization and a non-working deserialization, now unfortunately they've swapped round!
# simoneb said on 04 June, 2007 02:18 AM

Paul, did you mark the SingletonSerializationHelper class as serializable? Anyway, what kind of exception do you get?

# Paul said on 04 June, 2007 04:07 AM
Yes I did. I don't get any exceptions, it just stops outputting after the infomation. I don't think it outputs context. My save code is this Stream saveStream = File.Create(singletoninstance.datafolderlocation + "/save"); //save code serializer.Serialize(saveStream, singletoninstance); saveStream.Close(); and my load is this Stream loadStream = File.OpenRead(singletoninstance.datafolderlocation + "/save"); //load code singletoninstance = (singletonclass)(deserializer.Deserialize(loadStream)); loadStream.Close(); Before I made the changes (and if I comment out the SingletonSerializationHelper, GetObjectData and the : ISerializable line) the output works perfectly. Unfortunately I had the singleton being created twice problem this article is about. Could it be my save/load code? My singleton code is exactely the same as yours otherwise. Thanks.
# simoneb said on 04 June, 2007 06:05 AM

Paul,

I tried you code, with some minor modifications, and it works fine for me. Here's the code I tested:

BinaryFormatter serializer = new BinaryFormatter();

Stream saveStream = File.Create("save.dat"); 

//save code 
serializer.Serialize(saveStream, Singleton.Instance); 
saveStream.Close(); 
            
//and my load is this 
Stream loadStream = File.OpenRead("save.dat"); 
//load code 
Singleton newInstance = (Singleton)(serializer.Deserialize(loadStream)); 
loadStream.Close();
# JasonP said on 19 July, 2007 05:07 PM
You mention two ways; Serialization and by Reflection, but you only explain the Serialization. I think I might have the Reflection problem. :) I load assemblies dynamically (addins) and in one of them, it creates a singleton. My understanding, is that the assembly is loaded in the current AppDomain but I find myself with two instances of the same Singleton; one in the addin and one in the application. I have verified and they both seem to be running on the same AppDomain. Note that this code is being run in an ASP.NET project.
# simoneb said on 19 July, 2007 07:56 PM

Jason, I didn't explain reflection because it's pretty straightforward. Simply, it lets you bypass the constrain enforced by the private constructor, and lets you instantiate the class anyway. If you are using a plugin architecture it's likely that you are instantiating objects via reflection, but in this situation there's no straightforward mechanism to enforce singletons.

# Development With A Dot said on 08 September, 2008 06:15 AM

Everyone is writing singletons and everyone thinks they know what they are doing, but what if they're

This site

Search

Go

This Blog

News

Syndication

Sponsors

  • MaximumASP