Singletons

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

There is at least one scenario where things indeed can go wrong: serialization! SimoneB wrote about it in a blog post but I think she missed something. In fact, in .NET, there are at least two possible approaches to serialization:

While the two are similar - in fact, SoapFormatter and XmlSerializer can produce the same output - the way they work, the interfaces and attributes they rely on, are quite different:

I am assuming you know about singletons; to recall, the singleton pattern, as described in the famous Design Patterns book, is a software technique implemented to allow only a single, well known, object instance of a class. Let's start from the beginning, with a simple singleton class, shall we?

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();
    private Singleton() { }

    public static Singleton Instance { get { return (instance); } }
}

As you can see, this class cannot be derived from since it's sealed, cannot be instantiated (except if using reflection) because it has a private constructor, and can only be accessed through its static property Instance. But if we serialize it and afterwards deserialize it, we end up with two different instances of this class! This happens because .NET does not have any intrinsic support for singletons, they are written using common OOP techniques, however, it does have intrinsic support for serialization.

Using IFormatter-implementing classes does not work, because the class is not marked with the [Serializable] attribute, so we are protected from that side, but XmlSerializer does not require the use of this attribute in order to allow serialization, so we have a problem. XmlSerializer does, however, rely on interface IXmlSerializable, so if it is implemented, it's methods are called in the course of the serialization process. If we don't want our class to be serialized, we just have to do something on each of this methods that will prevent it from happening: throw an exception!

Here is the protected version of our code:

public sealed class Singleton: IXmlSerializable
{
    private static readonly Singleton instance = new Singleton();

    private Singleton() {}
    public static Singleton Instance { get { return (instance); } }

    XmlSchema IXmlSerializable.GetSchema() { throw(new NotSupportedException()); }
    void IXmlSerializable.ReadXml(XmlReader reader) { throw(new NotSupportedException()); }

    void IXmlSerializable.WriteXml(XmlWriter writer) { throw(new NotSupportedException()); }
}

And that's it! This way, there is no easy way to have two instances of the Singleton class, unless, of course, if using reflection: the two basic serialization methods supported by .NET both fail.

Feedback, including disagreement, is welcome! :-)

Bookmark and Share

                             

6 Comments

  • How about just throwing the exception in the private constructor if instance is not null?

  • Hi, Joe!

    Your are partially right; the problem, IMHO, is: there are ways to instantiate a type without running its constructor, such as when serializing an instance of an object for use with web services. See the FormatterServices.GetSafeUninitializedObject method.

  • Singletons.. Peachy :)

  • Singletons.. Ho-o-o-o-t :)

  • Singletons.. Retweeted it :)


  • That's not what the folks you are critiquing wanted to achieve. They wanted to be able to serialize objects which contain references to singletons, and then upon deserializing these objects have them once again refer to singletons. Blocking all serialization is not a viable solution.

    Imagine you have a Gender variable operating on a multiton pattern. You have 1000 person records, each containing a reference to Gender. In your program you there are just 2 instances of Gender. Male and Female. Now you would like to serialize your 1000 Person records to XML, and load them back in another AppDomain, with the knowledge that upon deserialization into the other AppDomain there will once again be only two Gender instances: Male and Female.

    Your "solution" is not a solution.

Comments have been disabled for this content.