Click here to Skip to main content
15,878,959 members
Articles / Programming Languages / C#

Static Class, Singleton and their Side Effects

Rate me:
Please Sign up or sign in to vote.
4.56/5 (54 votes)
22 Mar 2016CPOL8 min read 60.9K   340   68   28
This article questions the usage of singleton and static class and discusses about ways to avoid them.

Introduction

We often use static classes and singleton in our applications. Static classes are a convenient container to hold utility or helper methods which doesn’t have a state. Singleton on the other hand is heavily used for scenarios where you need to guarantee a single instance of an object throughout the application lifetime. The intention of this article is to examine the usage of static class and singleton in .NET and evaluate the tradeoffs that you make during their usage which includes code quality, testability and maintenance overhead. I’m sure some of you will look at static class and singleton in a different way (if you aren’t already :)) after reading this article.

Let's Begin...

For the benefit of those who not familiar with the concept of static modifier in C#, let me give a quick introduction.

When you declare a class as static, it guarantees that the class can't be instantiated, can't derive from or serve as the base for another type, and can contain only static members. You cannot create an instance of the static class and you must access its members by using the class name itself. This is because static members are part of the type itself as opposed to a specific object or type instance. We compromise on OOP completely by making a class static which we will discuss in the later part.

Let's look at the below example code. As you may see, a class can contain static and non-static members but a static class can hold only static members. Static members in a normal class is accessed directly without needing to create an instance of that class.

C#
public class StaticMethodTest
    {
        public string InstanceVariable { get; set; }
        public static string StaticVariable { get; set; }

        public static string StaticGreeting()
        {
            return "Morning";
        }

        public string InstanceGreeting()
        {
            return "Morning";
        }
    }

 public static class StaticClassTest
    {
        public static string StaticVariable { get; set; }

        public static string StaticGreeting()
        {
            return "Morning";
        }
    }  

Please not that static members cannot access non-static members as they need to be instantiated, allocate memory before using them.

C#
public class Program
   {
       static void Main(string[] args)
       {
           Console.WriteLine(StaticMethodTest.StaticGreeting());
           Console.WriteLine(new StaticMethodTest().InstanceGreeting());
           Console.ReadKey();
       }
   }

How Static Variables are Stored in Memory

Unlike instance variables, static variables are not directly stored in GC heap with a reference in stack (or inline). Let’s try to understand how a managed process works in CLR. There are mainly three app domains within a managed process. An app domain is an isolation for code execution within a process boundary. Most of the application runs in a default domain but you can explicitly create app domain if you want to. Default app domain is the place where most part of your application specific code is loaded and executed. Some part of it is loaded in the shared domain but discussion of that sort is outside the scope of this article.

Let's look at where static variables are stored. Static variables are an important constituent part of the MethodTable data structure which is stored in a special place in the memory called High Frequency Heap. MethodTable data structure is used to represent each class and interface loaded into an AppDomain. This is part of the class loading activity and it happens even before the object is ever created.

Image 1
(Figure 1)

All the primitive static types are inlined while the static value objects like structs and reference types are referred through OBJECTREFs created in the handle tables. OBJECTREF in the MethodTable refers to OBJECTREF in the AppDomain handle table, which refers to the heap-created object instance. Once created, OBJECTREF in the handle table will keep the object instance on the heap alive until the AppDomain is unloaded.

Image 2

Figure 2

To summarize, static variables are loaded before they are being used and never GC collected and its available as long as AppDomain is alive. It’s purely introducing a global state to your application like notorious global variables in languages like VB.

Singleton Design Pattern

Why would someone care about singleton? Singleton class allows you to maintain a single instance of itself throughout the application lifetime. While it breaks single responsibility principle by implementing its functionality as well as additional responsibility of creating its own instance, it is one of the well-known design patterns in software engineering.

Main Characteristics of a Singleton Design Pattern

  • A private and parameter less constructor which prevents other classes from instantiating a singleton class.
  • A public static variable which holds the reference to the only instance. This very public static variable introduces global state in your application!

One of the things that I would like to have in any application that I architect is enough transparency in dependency management. Singleton or static classes introduces transitive dependency. It makes your code tightly coupled and it becomes not testable in isolation. Unit testing would become a difficult task to handle.

Global Variables Are Evil so is Singleton?

Most of us agree to the fact that global variables are big no. They are evils and they spoil the beauty of your application and make it unmanageable. There are two aspects of Singleton. It’s a mechanism to assure a single instance of an object throughout the application life time and a global access to that variable. There are scenarios where you need to make sure only a single instance of an object is ever created and singleton does help you achieve that. There is no doubt about it but what I do not favor is the second part of it - the global state.

IoC Containers to the Rescue

Is there any other way we could possibly solve this problem without keeping a global state? I mean while we make sure only a single instance of a particular object is used through the app lifetime, we do not want to expose something in the root globally accessible to every other classes. The answer is yes and one of the approaches is to use something called IoC (Inversion of Control) containers. If you are already using IoC container in your project, there is no reason why you would ever use singleton pattern at all. You could assign IoC container the responsibility for resolving dependency and composing your application. It allows you to set the lifestyle of each object. All objects can be created within it rather than outsourcing some part of it to singleton.

Where Anything Static is Appropriate?

There are many scenarios where you would tempt to use static classes. Grouping all helper methods which doesn’t hold a state is one of them. Logging is another classical example but you could always inject the logger using an IoC container and set singleton scope to it. The only scenario, but not necessarily I would prefer using static is to define constants that needs to be shared across the application. As constants are immutable, they won’t introduce state inconsistencies in the system.

This is purely a personal choice and I’m sure many of you could differ from this view point.

Using the Code

Let’s make a coffee vending machine today and find out the difference between Static Class, Singleton Pattern and an Instance class. This application has mainly three objects named CoffeeCup, Coffee, and the CoffeeVendingMachine itself. The code sample has three implementations, one using instance class, singleton and a pure static implementation.

The Problem Statement

Create a coffee vending machine application and make sure only one instance of vending machine is available throughout the application life time, but you can have many instances of coffee cups and serve as many coffee as you want.

Image 3

Interface for coffeeVendingMachine:

C#
public interface ICoffeeVendingMachine
    {
        bool IsOn();
        void SwitchOn();
        void SwitchOff();        
        Stock CheckStock();
        void AddStock(Stock stock);
        void ServeCoffee(CoffeeType type, ISmartCoffeeCup cup);                
    }

Our SmartCoffeeCup. Intelligent enough to clean and empty by itself. :-)

C#
public interface ISmartCoffeeCup
    {
        Color Color { get; }
        bool IsEmpty { get; }
        bool IsClean { get; }
        void Clean();
        void Empty();
        void Fill(ICoffee coffee);
    }

Interface for coffee which has one property called CoffeeType.

C#
public interface ICoffee
   {
       CoffeeType Type { get; set; }
   }

An abstract class containing default implementation for some of the methods.

C#
public abstract class CoffeeVendingMachineBase : ICoffeeVendingMachine
   {
       #region fields

       protected Stock _rawMaterialStock;
       protected bool _isSwitchedOn;
       protected int _orderNumber { get; set; }

       #endregion

       #region abstract method signatures

       protected abstract bool IsOnInternal();
       protected abstract void SwitchOnInternal();
       protected abstract void SwitchOffInternal();
       protected abstract Stock CheckStockInternal();
       protected abstract void AddStockInternal(Stock stock);
       protected abstract void ServeCoffeeInternal(CoffeeType type, ISmartCoffeeCup cup);

       #endregion

       public CoffeeVendingMachineBase()
       {
           _rawMaterialStock = new Stock { Coffee = 0, Cream = 0, Sugar = 0 };
       }

       #region public methods

       public void AddStock(Stock stock)
       {
           try
           {
              AddStockInternal(stock);
              Audit("Successfully added stock");
           }
           catch (Exception ex)
           {
               Audit("Error while adding stock");
               throw;
           }
       }

       public Stock CheckStock()...
       protected virtual ICoffee PrepareCoffee(CoffeeType type)...
       public virtual void ServeCoffee(CoffeeType type, ISmartCoffeeCup cup)...
       public bool IsOn()...
       public void SwitchOff()..
       public void SwitchOn()...

       #endregion

       #region private methods

       private void Audit(string message)
       {
           Console.WriteLine(message);
       }

       #endregion
   }

Static Implementation

Time to forget about all OOP techniques that we did above. No matter what, they can’t be used in a static class as it doesn’t support inheritance. Like you may see in the below code, we have a static container class with all static member implementations. How do we leverage our OOPs techniques then and keep a single copy of the object throughout the app lifetime? Whala... Did you just forget about our best friend Singleton?

C#
public static class CoffeeMachine 
    {
        #region properties
        public static Stock RawMaterialStock
        {
            get
            {
                return _rawMaterial;
            }
        }

        #endregion

        #region private fields
        private static Stock _rawMaterial { get; set; }
        private static bool _isOn { get; set; }
        private static int _orderNumber { get; set; }
        #endregion

        public static void ServeCoffee(CoffeeType type, ISmartCoffeeCup cup)...
        public static ICoffee PrepareCoffee(CoffeeType type)...
        public static void CheckStock()...
        public static void AddStock(Stock stock)...
        public static void SwitchOn()...
        public static void SwitchOff()...        
    }

Singleton Implementation

Wow... I could finally use my OOP techniques and a single instance is always guaranteed!

C#
using System;
using Sample.Core;
namespace Sample.Singleton
{
    public sealed class CoffeeVendingMachine: CoffeeVendingMachineBase, ICoffeeVendingMachine
    {
        private static CoffeeVendingMachine _instance = null;
        private static readonly object padLock = new object();

        CoffeeVendingMachine()
        {
            _rawMaterialStock = new Stock { Coffee = 0, Cream = 0, Sugar = 0 };
        }

        public static CoffeeVendingMachine Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (padLock)
                    {
                        if (_instance == null)
                        {
                            _instance = new CoffeeVendingMachine();
                        }
                    }
                }
                return _instance;
            }
        }

        protected override bool IsOnInternal()...  
        protected override void SwitchOnInternal()...
        protected override void SwitchOffInternal()...       
        protected override Stock CheckStockInternal()...
        protected override void AddStockInternal(Stock stock)...
        protected override void ServeCoffeeInternal(CoffeeType type, ISmartCoffeeCup cup)...      
    }
}

A simple console app to check if our solution works.

C#
namespace Sample.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //Singleton Test

            var cup = new CoffeeCup(Color.White);
            var singletonMachine= Sample.Singleton.CoffeeVendingMachine.Instance;
            singletonMachine.AddStock(new Stock() { Coffee = 200, Cream = 200, Sugar = 200 });
            singletonMachine.SwitchOn();
            singletonMachine.CheckStock();
            singletonMachine.ServeCoffee(CoffeeType.Cappuccino, cup);
            singletonMachine.ServeCoffee(CoffeeType.Espresso, cup);
            singletonMachine.CheckStock();
            Console.ReadLine();
        }
    }
}

Console output:

Image 4

End of the story? Not really! How about avoiding the evil, the global state?

Instance Implementation

Nothing much changed but we do not have the static instance reference in singleton here. How would it guarantee a single copy then? Well... here comes IoC container, Castle Windsor.

C#
using System;
using Sample.Core;
namespace Sample.Instance
{
    public class CoffeeVendingMachine : CoffeeVendingMachineBase, ICoffeeVendingMachine
    {
        public CoffeeVendingMachine()
        {
            _rawMaterialStock = new Stock { Coffee = 0, Cream = 0, Sugar = 0 };
        }

        protected override bool IsOnInternal()...       
        protected override void SwitchOnInternal()...        
        protected override void SwitchOffInternal()...       
        protected override Stock CheckStockInternal()...  
        protected override void AddStockInternal(Stock stock)...
        
        protected override void ServeCoffeeInternal(CoffeeType type, ISmartCoffeeCup cup)
        {
            if (!_isSwitchedOn)
            {
                Console.WriteLine("Coffee machine is off.");
                return;
            }
            _orderNumber += 1;
            var coffee = PrepareCoffee(type);
            if (!cup.IsClean) cup.Clean();
            if (coffee != null) cup.Fill(coffee);
            Console.WriteLine(string.Format("Order Number:{0}", _orderNumber));
            Console.WriteLine(string.Format("Successfully filled coffee in {0} cup", cup.Color.Name));
            Console.WriteLine("---------------------------------");
        }        
    }
}

Time to configure IoC container. We are using castle Windsor in this project. There are very good articles in the web which would help you configure Castel for resolving dependencies. A detailed discussion about IoC is outside the scope of this article.

C#
public class DependencyResolver
    {
        private static IWindsorContainer _container;

        //Initialize the container
        public static void Initialize()
        {
            _container = new WindsorContainer();
            _container.Register(new ComponentRegistration());
        }

        //Resolve types
        public static T For<T>()
        {
            return _container.Resolve<T>();
        }
    }

Register the Components in Castle:

C#
public class ComponentRegistration : IRegistration
    {
        public void Register(IKernelInternal kernel)
        {
            kernel.Register(
                 Component.For<ISmartCoffeeCup>().ImplementedBy<CoffeeCup>().LifestyleTransient(),
                 Component.For<ICoffeeVendingMachine>()
                 .ImplementedBy<CoffeeVendingMachine>().LifestyleSingleton());
        }
    }

A unit test project (xUnit) for testing if our solution works.

C#
public class InstanceTests
    {
        public InstanceTests()
        {
            //Initialize the dependency resolver
            DependencyResolver.Initialize();
        }

        [Fact]
        public void When_First_Machine_Is_On_Second_Is_On()
        {
            var firstMachine = DependencyResolver.For<ICoffeeVendingMachine>();
            firstMachine.SwitchOn();
            var secondMachine = DependencyResolver.For<ICoffeeVendingMachine>();
            Assert.Equal(firstMachine.IsOn(), secondMachine.IsOn());            
        }

        [Fact]
        public void Stock_Matches_On_Both_Machine_After_Serving_Coffee()
        {
            var firstMachine = DependencyResolver.For<ICoffeeVendingMachine>();
            firstMachine.AddStock(new Stock() { Coffee = 100, Cream = 100, Sugar = 100 });
            firstMachine.SwitchOn();
            var cup = new CoffeeCup(Color.Violet);
            firstMachine.ServeCoffee(CoffeeType.Cappuccino, cup);
            var firstmachineStock = firstMachine.CheckStock();
            var secondMachine = DependencyResolver.For<ICoffeeVendingMachine>();
            var secondMachineStock = secondMachine.CheckStock();
            Assert.Equal(firstmachineStock.Cream, secondMachineStock.Cream);
        }       
    }

Not a surprise, our solution works. Thanks to IoC container!

Final Words

While this article is opinioned and inclines towards a non-static way of programming, it’s perfectly okay to have a conflicting view on this topic but please do post your comments below.

Enjoy your coffee. :)

History

  • 20th March, 2016: Version 1.0 - Initial article submitted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
GeneralMy vote of 5 Pin
renatomayer11-May-17 20:09
renatomayer11-May-17 20:09 
GeneralMy vote of 4 Pin
Nathan Minier19-Sep-16 2:40
professionalNathan Minier19-Sep-16 2:40 
QuestionICoffeeVendingMachine question Pin
xszaboj11-Apr-16 2:23
xszaboj11-Apr-16 2:23 
GeneralMy vote of 5 Pin
Mazme2-Apr-16 20:40
Mazme2-Apr-16 20:40 
GeneralMy vote of 4 Pin
Donsw2-Apr-16 16:21
Donsw2-Apr-16 16:21 
SuggestionStatic Class, Singleton and their Side Effects Pin
Dmitriy Gakh1-Apr-16 6:03
professionalDmitriy Gakh1-Apr-16 6:03 
GeneralMy vote of 1 Pin
DaveKolb29-Mar-16 7:52
DaveKolb29-Mar-16 7:52 
Makes the code much more obfuscated and is a perversion of IoC!
QuestionHiding behind IoC... Pin
DaveKolb29-Mar-16 7:51
DaveKolb29-Mar-16 7:51 
Questionstatic operator overloading Pin
Marky Collins28-Mar-16 6:30
Marky Collins28-Mar-16 6:30 
AnswerRe: static operator overloading Pin
Rafael Nicoletti3-Apr-16 15:12
Rafael Nicoletti3-Apr-16 15:12 
QuestionMetrics Pin
threegreen25-Mar-16 14:26
threegreen25-Mar-16 14:26 
GeneralMy vote of 5 Pin
Brian Kinnish24-Mar-16 13:38
Brian Kinnish24-Mar-16 13:38 
QuestionNot sure what problem you're trying to solve. Pin
BradleyInKona24-Mar-16 9:46
BradleyInKona24-Mar-16 9:46 
Questionconfused statement near the beginning Pin
Member 1202398824-Mar-16 1:30
Member 1202398824-Mar-16 1:30 
AnswerRe: confused statement near the beginning Pin
Member 920074624-Mar-16 7:20
Member 920074624-Mar-16 7:20 
GeneralRe: confused statement near the beginning Pin
R.D.H.24-Mar-16 10:33
R.D.H.24-Mar-16 10:33 
GeneralRe: confused statement near the beginning Pin
Member 1202398827-Mar-16 17:38
Member 1202398827-Mar-16 17:38 
GeneralMy vote of 3 Pin
_Vitor Garcia_23-Mar-16 5:49
_Vitor Garcia_23-Mar-16 5:49 
Generaltoo many concepts... Pin
John Torjo22-Mar-16 23:03
professionalJohn Torjo22-Mar-16 23:03 
GeneralRe: too many concepts... Pin
wmjordan20-Jun-16 2:36
professionalwmjordan20-Jun-16 2:36 
GeneralMy vote of 5 Pin
dmjm-h22-Mar-16 12:09
dmjm-h22-Mar-16 12:09 
GeneralThoughts about Singleton Pin
Klaus Luedenscheidt20-Mar-16 19:57
Klaus Luedenscheidt20-Mar-16 19:57 
GeneralRe: Thoughts about Singleton Pin
Layinka22-Mar-16 2:40
professionalLayinka22-Mar-16 2:40 
GeneralRe: Thoughts about Singleton Pin
John Brett22-Mar-16 5:05
John Brett22-Mar-16 5:05 
QuestionNot clear how the stat can be maintained without Static and Singleton Pin
Member 1071148120-Mar-16 7:47
Member 1071148120-Mar-16 7:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.