Welcome to MSDN Blogs Sign in | Join | Help

D Melodic Minor Accidental

A conglomeration of random thoughts on C#, .NET, OOP, and working at Microsoft

Syndication

Other Sites

More Singleton

Ok, so I lied: I'm not moving on to unions quite yet.  Cyrus brought up a great point on the last post about .NET serialization, as well as the lack of thread safety in my third example.  Due to the shared nature of singletons, I have to agree that thread-safety is an important enough requirement to mandate an expansion of the singleton story...

In addition, Andrew provided a great link about some other thoughts on implementing singletons in .NET.

http://www.yoda.arachsys.com/csharp/singleton.html

It introduces a neat way to resolve the thread safety by relying on the lazy loading of types in .NET.  Although the article from arachsys.com doesn't mention serialization, if you read the MSDN reference on System.Runtime.Serialization.SerializationInfo, its example includes a way to control reinstantiation during deserialization in order to obtain the shared instance instead of the default serialization behavior of invoking a constructor.  In doing so, you have to create a type that proxies for you during serialization -- but there's no reason why it shouldn't be the same nested type used for delay loading of the lazy-shared instance.  Thus, whip in some explicit interface implementations (comment if you really want me to cover this topic), and voila!, you get the following:

[Serializable]

sealed class Foo : ISerializable {

      // change the default constructor to private

      private Foo() { }

 

      public static Foo Only {

            get { return SerializationProxy.sharedOnly; }

      }

 

      public object DoFunctionality();

 

      [Serializable]

      private class SerializationProxy : IObjectReference {

            internal static readonly Foo sharedOnly = new Foo();

            object IObjectReference.GetRealObject(StreamingContext context) {

                  // When deserializing this object, return a reference to

                  // Foo's singleton object instead.

                  return sharedOnly;

            }

      }

 

      [SecurityPermission(SecurityAction.Demand,SerializationFormatter=true)]

      void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {

            info.SetType(typeof(Foo.SerializationProxy));

      }

}

The example differs from both the MSDN example and the arachsys.com article, but I'm pretty convinced it's the right thing to do.  Creating a new type for serialization proxying is pretty much necessary unless you want to change the constructor semantics for the singleton type itself.  Consider the following:

Assume type Foo is its own serialization proxy.  During deserialization, the proxy instance of Foo is deserialized by default serialization means -- which involves invoking some constructor (either compiler provided default constructor, or user-coded).  Thus you lose the invariant that any instance constructor is only invoked once, since some instance constructor has to be invoked at every proxy deserialization before GetRealObject() is invoked on the proxy.

 

It seems better to “de-singleton” the proxy type and hide it away, and a private nested type comes natural to my mind.  Since it's nested, it allows us to also follow the arachsys.com and change the loading of the actual singleton instance to a form of "lazy loading", without ever having to introduce a synchronization keyword (e.g. "lock") or use the synchronization API.  Albeit, the code doesn't really reflect in its structure that it's particularly both threadsafe and lazy-loading.

 

So, why do we really have to worry about serialization? Mostly because any of these implementations only guarantee one instance per AppDomain, which is somewhat like a process-like abstraction for .NET, except you can have more than one AppDomain per process.  Each AppDomain has its own copies of static data though, so you can shield the effects on one against the other.  Sometimes this is what you want to do (say you're running some untrusted code).  However, this also prevents them from really sharing singleton instances.  Also, when you have two .NET programs communicating across different processes (either concurrently, or different invocations using saved state), or on different computers, serialization becomes key.

 

I still probably use, in practice, the first or second simplistic singleton implementation -- with the addition of serialization support if necessary... but covering this topic has definitely brought new light to my own design toolbox -- I'll probably opt for this nested-lazy-serializable implementation a lot more in the future.

Published Friday, May 21, 2004 3:29 AM by TheoY

Filed under:

Comments

# re: More Singleton @ Friday, May 21, 2004 3:42 AM

Theo: "The example differs from both the MSDN example and the arachsys.com article, but I'm pretty convinced it's the right thing to do"

Writing a single test would probably be the best thing to do here and then you would not only convince yourself, but me as well :-)

Cyrus Najmabadi

# re: More Singleton @ Friday, May 21, 2004 5:49 AM

Theo: thanks for the summary. It's been a rather interesting discussion. I'm looking forward to your next article :)

Andrew

# re: More Singleton @ Friday, May 21, 2004 12:21 PM

First, a question: Why are you overly concerned about controlling the lifecycle of a single object instance when it contains absolutely no state? Wouldn't a static class fulfill the same requirement (without having to worry about synchronization and other object lifecycle related concerns)?

Second, a comment: I'm not a big fan of Singletons. The pattern is majorly overused, most likely due to its simplicity and perhaps since it's among the first patterns detailed in the GoF book. There aren't that many cases in which a truly global object is needed, and resorting to a Singleton is often just a lazy way of solving the following problem: Well, I know there's some sort of context to which this object belongs, but it's really a pain to pass the context around and I don't know how to do it in an object oriented fashion, so poof I'll create the equivallent of a global variable.

Not trying to cause any heartburn, but I wanted to toss my opinion into the mix.

Joe Duffy

# re: More Singleton @ Friday, May 21, 2004 12:38 PM

Good question/comment, Joe.

One of the nicer payoffs of a singleton pattern with true instance uniqueness is the ability to use reference equality, instead of dispatching to a .Equals() method. Thus, although C# allows you to override the '==' operator, by creating a singleton, you don't need to. In addition, object.ReferenceEquals(,) still works.

A static class doesn't always fulfill the same niche as a singleton, because a class can't always substitute an object. Sure, you can get the object that represents the classes's type by using typeof(Foo), but that object returned doesn't declare the methods that Foo provides.

Using singleton objects instead of classes allow you to swap out that particular object. Let me bring up the stream example again.

TextWriter stream = Console.Out;

if(user wants stderr instead) {
stream = Console.Error;
}

stream.WriteLine("write my logging here...");
stream.Write("use a different method...");
stream.WriteLine();

One might argue that at times, delegates to methods of a static class are a reasonable implementation too. Certainly this is the case if you're only concerned about one method (as many a .NET designer will tell you that delegates are often a good way to replace an interface that only has one method). However, by instantiating singleton objects instead of using a static class, you can swap out the functionality of all of the methods at the same time, without doing multiple assignments -- which may be difficult to keep consistent.

Also, you bring up a good point about global variables -- I agree, using a singleton as a global variable is almost always a horrible idea. Use of singletons should be more like global *constants*. People use global constants all the time. One can argue that the idea of "zero" is a global constant, or "false" and "true" are also global constants. The emphasis is on constant, that's why it's imperative that singletons should contain state (shouldn't mutate).

I don't always agree with GoF too, but I think in this case, I needed to start with singleton since it (or its ideas) tend to make its ways into many other "patterns".

Definitely no heartburn experienced Joe, and thanks a bunch for posing these questions so we could cover these issues. I hope the explanation is clear, but by all means ask further questions if needed.

Theo Yaung

# re: More Singleton @ Thursday, May 27, 2004 3:16 AM

A few more things to ponder on this topic...

What are the implications of the following on the initialisation of the singletin:
1. Multiple processors
2. Processors with weak memory ordering (such as Intel Itanium processors)?

Your thoughts and comments on this article:

http://discuss.develop.com/archives/wa.exe?A2=ind0203B&L=DOTNET&P=R375

Andrew

Andrew

New Comments to this post are disabled
Page view tracker