GOTCHA 20 Singleton isn t guaranteed process-wide


GOTCHA #20 Singleton isn't guaranteed process-wide

A static/Shared field belongs to the class and is not part of any instance. Typically, when you see a static/Shared field, you know that no matter how many instances of the class exist, there is one and only one occurrence of this field. Often a static/Shared field is used to limit the number of instances of an objectthe concept of Singleton. (Refer to "Exploring the Singleton Design Pattern" and "Implementing the Singleton Pattern in C#" in the section "on the web" in the Appendix for very good articles on this topic.) A singleton takes measures to make sure that no more than one instance of its type can be created in an application. One way to do this is to make the constructor of the class protected or private, and to provide a static/Shared method to fetch the object, as shown in Example 3-1.

Example 3-1. Example of a singleton

C# (SingletonAppDomain)

 using System; namespace Singleton {     public class MySingleton     {         public readonly DateTime creationTime;         protected MySingleton()         {             creationTime = DateTime.Now;         }          protected static MySingleton theInstance =             new MySingleton();         public static MySingleton GetInstance()         {             return theInstance;         }     } } 

VB.NET (SingletonAppDomain)

  Public Class MySingleton     Public ReadOnly creationTime As DateTime     Protected Sub New()         creationTime = DateTime.Now     End Sub      Protected Shared theInstance As New MySingleton     Public Shared Function GetInstance() As MySingleton         Return theInstance     End Function End Class 

The MySingleton class is written so that at most one instance can be created. But here's the gotcha: the unit of granularity for static/Shared fields in .NET is not the process, but the AppDomain. (Application domains provide isolation, unloading, and security boundaries for executing managed code.) And a process can contain more than one AppDomain. So the above code restricts MySingleton to one instance only within its AppDomain, but not within the entire process, as the code in Example 3-2 demonstrates.

Example 3-2. Singleton within AppDomain

C# (SingletonAppDomain)

 using System; using System.Threading; using System.Reflection; namespace Singleton {     class Test : MarshalByRefObject     {         public void Run()         {              MySingleton object1 = MySingleton.GetInstance();             Console.WriteLine("Object created at {0}",                 object1.creationTime.ToLongTimeString());             Thread.Sleep(1000);              MySingleton object2 = MySingleton.GetInstance();             Console.WriteLine("Object created at {0}",                 object1.creationTime.ToLongTimeString());         }         [STAThread]         static void Main(string[] args)         {             Test anObject = new Test();              anObject.Run();             Thread.Sleep(1000);             AppDomain domain =                  AppDomain.CreateDomain("MyDomain");             Test proxy =                 domain.CreateInstance(                     Assembly.GetExecutingAssembly().FullName,                 typeof(Test).FullName).Unwrap() as Test;              proxy.Run();             Thread.Sleep(1000);             anObject.Run();         }     } } 

VB.NET (SingletonAppDomain)

 Imports System.Threading Public Class Test         Inherits MarshalByRefObject     Public Sub Run()          Dim object1 As MySingleton = MySingleton.GetInstance()         Console.WriteLine("Object created at {0}", _          object1.creationTime.ToLongTimeString())         Thread.Sleep(1000)          Dim object2 As MySingleton = MySingleton.GetInstance()         Console.WriteLine("Object created at {0}", _          object1.creationTime.ToLongTimeString())     End Sub     Public Shared Sub Main()         Dim anObject As Test = New Test          anObject.Run()         Thread.Sleep(1000)         Dim domain As AppDomain = _             AppDomain.CreateDomain("MyDomain")         Dim proxy As Test = _           CType( _               domain.CreateInstance( _                 System.Reflection. _                 Assembly.GetExecutingAssembly().FullName, _                 GetType(Test).FullName).Unwrap(), Test)          proxy.Run()         Thread.Sleep(1000)         anObject.Run()     End Sub End Class 

In the above code you call the GetInstance() method of MySingleton from within the Test class's Run() method. Then you create an object of Test within another AppDomain and call Run() on it. The output from the program is shown in Figure 3-1.

Figure 3-1. Output from Example 3-2


Notice that the four calls to GetInstance() made from within the default AppDomain (that is, calls to Run() from within the Main() method) fetch the same object of MySingleton (as seen in the first two and the last two statements of output). However, the calls to GetInstance() from the AppDomain you created produce a different instance of the MySingleton class.

In the example you create an AppDomain explicitly, so you at least know of its existence. There are times, however, when an AppDomain is created by the .NET framework (like in ASP.NET) or other APIs you may use without your being aware of it. The behavior of singleton is no different in those cases.

There is an excellent discussion of how and why .NET creates new AppDomains in [Lowy03].

IN A NUTSHELL

A class's static/Shared fields are unique only in the AppDomain where the class is loaded. Each new AppDomain created in your application produces a new copy of them.

SEE ALSO

Gotcha #27, "Object initialization sequence isn't consistent."



    .NET Gotachas
    .NET Gotachas
    ISBN: N/A
    EAN: N/A
    Year: 2005
    Pages: 126

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net