Skip to content

lsauer/csharp-singleton

Repository files navigation

## Singleton - A generic, portable and easy to use Singleton pattern implementation #####           for .NET Core / C#

Join the chat at https://gitter.im/csharp-singleton/Lobby


author profile for Lo Sauer on Stack Exchange, a network of free, community-driven Q&A sites
website https://github.com/lsauer/csharp-singleton
license MIT license
current Build Status Build status Coverage Status
package PM> Install-Package CSharp.Portable-Singleton
description A generic, portable, documented and easy to use Singleton pattern implementation, to enforce and manage single instances
documentation complete reference v2.0.0.4
suported

Table of Contents

Download

Full Version NuGet Build NuGet Install
CSharp.Portable-Singleton Build Status Build status Coverage Status PM> Install-Package CSharp.Portable-Singleton

Social: Twitter Twitter Follow Facebook Like

Documentation

Please visit here for a complete reference, which is also included in the NuGet package.

Setup

  1. Install the package with the NuGet Package manager: PM> Install-Package CSharp.Portable-Singleton.
  2. Add the namespace to your code: using Core.Singleton;.
  3. Derive the supposed singleton class, elevating it to the logical singleton-class: MySingleton : Singleton<MySingleton>.
  4. Done! Use interfaces, delve into the documentation and take a look at best practices.

Find below an example to provide a glimpse of what the code will look like in practice:

   using Core.Singleton;
   public class AClass : Singleton<AClass>
   {
       // a public parameterless constructor is required
       public AClass()  { }

       public AMethod() { Console.Write("Write called"); }
   }
   AClass.CurrentInstance.AMethod();
   System.Diagnostics.Debug.Assert( ReferenceEquals(new AClass(), AClass.CurrentInstance, 
   "Same Instance");

Background

.NET does not particularly enforce software design patterns. The singleton pattern is of notable use in software as a creational design pattern, wherein only one instance of an object may be instantiated, thus generally extending the usefulness of singletons to the creation or wrapping of single-access resources.

Creating a new singleton is straightforward: Declaring an inheritance of the intended singleton class to the generic singleton class Singleton<> suffices.
Such as:

    internal class MyClass : Singleton<MyClass> {
        ...  
    }

A usage example for singletons would be an improved console wrapper for .NET console applications, other typical scenarios would be such where performance and synchronizing aspects are brought to bear.

Note: Arguably large scale applications running on modern platforms can resort to improved solutions over singletons particularly through framework support of design patterns.

Initialization

To get started, it is recommended to adhere to the following Syntax:

namespace MyNamespace {
    using Core.Singleton;
    
    public class MyClass : Singleton<MyClass> { };

    var somePropertyValue = Singleton<MyClass>.CurrentInstance.SomeProperty;

    // ...and for a method:

    var someMethodValue = Singleton<MyClass>.CurrentInstance.Add(1, 2);
}

There are several other ways to initialize a new Singleton<T> instance, wherein T is the type of the respective logical singleton class, refering to the class implementing the custom logic.

Ways of initialization

  • Accessing Singleton<T>.CurrentInstance or Singleton<T>.Instance for the first time
  • Creating a new explicit instance: new T()
  • Using SingletonAttribute such as [Singleton]class T : Singleton<T>{...} and subsequently calling Initialize() from a Singletonmanager instance
  • Utilizing Activator.CreateInstance(typeof(T));
  • With a custom parameterized class-constructor and instancing the class T with new T(...)
  • Utilizing the SingletonManager (see below)
  • By using the TypeInfo Extension Method ToSingleton() e.g. typeof(MyClass).GetTypeInfo().ToSingleton()
  • Please refer to the Examples for code and case scenarios

Singleton Properties

The generic Singleton<T> construct has the following static properties, which are referenced in \Enum\SingletonProperty.cs:

        [Description("The current or created instance of the singleton")]
        CurrentInstance = 1 << 1,
        
        [Description("The internally created instance of the singleton")]
        Instance = 1 << 2,
        
        [Description("Gets whether the singleton of type TClass is initialized")]
        Initialized = 1 << 3,

        [Description("Gets whether the singleton of type TClass is disposed")]
        Disposed = 1 << 4,

        [Description("Gets whether the singleton of type TClass is blocked for handling")]
        Blocked = 1 << 5,

In special cases disposal is helpful or even necessary. See the Examples for cases.

Singleton Instances & Checks

  • To check if the instance or type is a Singleton, use the Syntax: myobj is ISingleton
  • To check if a type is a Singleton, use the Syntax typeof(MyClass).GetTypeInfo().IsSingleton()

Respectively, omit the call to GetTypeInfo() as shown above, if the comparison type is already a TypeInfo instance.

  • To check if the singleton was created internally, you may check if the property (Instance == null)

Singleton Events

The following properties follow the convention of INotifyPropertyChanged but do not implement it, whilst using a custom typed SingletonPropertyEventHandler instead of the cannonical PropertyChangedEventHandler.

The event PropertyChanged itself is declared static to allow listening to Disposed and Initialized even when the singleton instance itself is disposed and free for garbage collection.

    public static event SingletonEventHandler PropertyChanged;

Additionally, an event is triggered when the property Manager changes. This property is used for setter dependency injection of a SingletonManager instance implementing ISingletonManager.

In case of several singleton classes in a given project, it is recommended to use and pass around a SingletonManager instance.

For instance to listen to the Disposed event for post-cleanup tasks, during the shutdown or exiting of an application, one may use a similar code-sample as follows:

    Singleton<MyClass>.PropertyChanged += (sender, arg) => {
        if(arg.Property == SingletonProperty.Disposed){ 
            ... 
        }
        ...
    };

    //... prep the application until it is sensible to init the singleton
    var logger = Singleton<RenderLogger>.GetInstance();

Note, that the singleton does not even have to be initialized at this point, making it safe to intialize typical IStream elements within the singleton constructor.

The SingletonPropertyEventArgs

The EventHandler of PropertyChanged passes an instance of ISingleton as the first argument, and as second parameter an instance of SingletonPropertyEventArgs, which contains the following properties:

  • Name : a string containing the name of the changed property
  • Value: the boxed current value of the property
  • Property: the property encoded as an enum value of SingletonProperty

The following code excerpt creates a new SingletonPropertyEventArgs instance:

    var propertyName = SingletonProperty.Instance.ToString();
    var propertyValue = 100;
    var args = new SingletonPropertyEventArgs(SingletonProperty.Initialized, propertyValue);

The following example demonstrates the dynamic use of GetValue within an EventHandler, to access singleton properties not known until runtime.

    Singelton<MyClass>.PropertyChanged += (sender, arg) =>
            {
                if (arg.Property == SingletonProperty.Initialized)
                {
                    var value = sender.GetValue("Value");
                }
            };

Generally, it is recommended to accss properties of similar singletons through custom interfaces (i.e. ISingletonTemplate<TCommonDenominator>) and perform specific typechecks using the is operator alongside explicit casts:

    Singelton<MyClass>.PropertyChanged += (sender, arg) =>
            {
                if (arg.Property == SingletonProperty.Initialized)
                {
                    if(sender is MyClass /*check including inherited types*/){
                        var senderTyped = sender as MyClass;
                        senderTyped.SetDateTime(DateTime.Now);
                    }else if( sender.GetType() == typeof(MyStrictClass) /*check excluding inherited types*/){
                        var senderTyped = sender as MyStrictClass;
                        Console.WriteLine(senderTyped.SayHello());
                    }else{
                        return;
                    }
                    // do something else if the type got matched
                }
            };

Example Usage

In the following example the class AClass implements the 'singleton business logic', and inherits from Singleton<>.
It suffices to include the assemblies, namespaces and derivation : Singleton<AClass> to get the expected, tested behavior:

Example1: Typical Usage
    using Core.Extensions.
    public class AClass : Singleton<AClass>
    {
        public string AMethod( [CallerMemberName] string caller = "" )
        {
            return caller;
        }

        public static string AStaticMethod( [CallerMemberName] string caller = "" )
        {
            return caller;
        }
    }

    static void Main( string[] args )
    {
        Console.WriteLine("Running: " + typeof(Program).Namespace + ". Press any key to quit...");

            var aClass = new AClass();
            Console.WriteLine("Expected: 'Main'; Observed: '{0}'", aClass.AMethod());
            Console.WriteLine("Expected: 'Main'; Observed: '{0}'", AClass.CurrentInstance.AMethod());
            Console.WriteLine("Expected: 'Main'; Observed: '{0}'", AClass.AStaticMethod());
            object bClass = null;
            try
            {
                bClass = new AClass();
            }
            catch (SingletonException exc)
            {
                if (exc.Cause == SingletonCause.InstanceExists)
                    bClass = AClass.CurrentInstance;
            }
            var condition = Object.ReferenceEquals(aClass, bClass);
            //> true

        var input = Console.ReadKey(true);
    }

Note: Many more examples are provided in full, within the examples folder.

Result:

This example above will yield the expected outcome of:

Running: Examples.Example1. Press any key to quit...
Expected: 'Main'; Observed: 'Main'
Expected: 'Main'; Observed: 'Main'
Expected: 'Main'; Observed: 'Main'

SingletonException

A Singleton class can throw a SingletonException (See Fig 1).

These are referenced in \Enum\SingletonCause.cs.

        [Description("Indicates the default or unspecified value")]
        Unknown = 1 << 0, 

        [Description("Indicates an existing Singleton instance of the singleton class `T`")]
        InstanceExists = 1 << 1, 

        [Description("Indicates that the created Singleton instance does not have a parent class")]
        NoInheritance = 1 << 2, 

        [Description("Indicates that an exception by another class or module was caught")]
        InternalException = 1 << 3, 

        [Description("Indicates that the Singleton must not be instanced lazily through an Acccessor, but the instance explcitely declared in the source-code")]
        NoCreateInternal = 1 << 4, 

        [Description("Indicates that the Singleton must not be disposed")]
        NoDispose = 1 << 5, 

        [Description("Indicates an existing mismatch between the singleton class `T` and the logical singleton class or parent-class invoking the constructor")]
        InstanceExistsMismatch = 1 << 6,

SingletonAttribute

For global initialization as well as constriction the purpose of a singleton, the logical Singleton class should always be attributed with [Singleton] as shown in the following code example:

      [Singleton(disposable: false, initByAttribute: false, createInternal: true)]
      public class AClass : Singleton<AClas> {
        ... 
      }

The attribute has three accessible properties:

  • Disposable (default=false): Set to true if the is allowed to be disposed
  • CreateInternal (default=true): Set to false if the Singleton is only supposed to be instantiated externally by explicit declaration within the user source-code
  • InitByAttribute (default=true): Set to true to allow joint initialization by the SingletonManager method Initialize

SingletonManager - Managing several Singletons

To manage several singleton types and instances throughout a large application, use the SingletonManager class as follows:

The following example iterates over a Pool of Singletons and performs logic dependent on the type of singleton:

    var singletonTypes = new List<Type>() { typeof(ParentOfParentOfAClass), typeof(ParentOfAClass), typeof(IndispensibleClass) };
    // create the singletons and add them to the manager
    var singletonManager = new SingletonManager(singletonTypes);

    foreach (var singleton in singletonManager.Pool)
    {
        if (singleton.Value is ParentOfParentOfAClass)
        {
            var instanceTyped = singleton.Value as ParentOfParentOfAClass;
            Console.WriteLine($"POPOAClass ImplementsLogic: {instanceTyped.ImplementsLogic}");
        } else {
            Console.WriteLine(singleton.Value.GetType().FullName);
        }
    }

The singletonManager.Pool property provides access to a thread-safe, ConcurrentDictionary<Type, ISingleton> instance which allows for writing queries in familiar LINQ Syntax.
Disposed Singletons are never deleted but are set to null using the SingletonManager's AddOrUpdate method.

Creating instances

To create new instances of a known type use the generic CreateInstance method as follows:

     var singletonManager = new SingletonManager();
     var gameStatics = singletonManager.CreateSingleton<GameStatics>();

If the type is only known at runtime or available dynamically pass the type as argument, as shown in the following code example:

     var singletonManager = new SingletonManager();
     var getInstance = (type) => {
        var gameStatics = singletonManager.CreateSingleton(type);

     };
     getInstance(typeof(GameStatics));

Serialization / Deserialization

There is nothing in the Singleton class itself that would prevent the implementation of a serializer, however the implementation as well as testing is in the hands of the developer.

Generic solutions are not recommended but rather specific, tested implementations of those singletons where necessary. For instance in a Hibernate / Resume state scenario. It is recommended to use extend the SingletonManager for that purpose.

Also take a look at this discussion

Tests

This library has been tested by the XUnit Testing Framework. Tests are run with several classes, one of with AClass adhering to a straightforward cannonical inheritance schema:

Fig 1:

Bugs GitHub issues

Should you be certain that you ran into a bug, please push a new issue here.

Notes

In a nested inheritance scenario of several classes inheriting each other hierarchically, and a determined base class deriving from singleton, it is important to define the logical singleton class. This is the class intended to implement the logic of the singleotn following the Single Responsibility Pricipal.

It it also the class that determines the generic type T from which the base class - the class short of Singleton<T> itself, must inherit Singleton<T>

Nested Inheritance Example

For a more complex inheritance singleton scenario, please refer to README_Example_Advanced.md

Best practices

To maintain good readability when using this library:

  • expose logical methods and properties within one 'logical' singleton-class
  • avoid static accessor access from any other type than the one passed as generic parameter to the singleton<T>: e.g. ParentOfParentOfAClass.Instance is OK, but avoid AClass.Instance
  • attribute the singleton class according to the singleton's purpose by using the SingletonAttribute
  • use interfaces for common properties and methods and single out methods and accessors that do not necessarily have to underly a singleton

Useful links:

About

A generic, portable and easy to use Singleton pattern implementation for DotNet, to enforce a pattern of singular instance creation

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages