author | |
---|---|
website | https://github.com/lsauer/csharp-singleton |
license | MIT license |
current | |
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 |
|
- Download
- Documentation
- Setup
- Background
- Initialization
- Singleton Properties
- Singleton Instances & Checks
- Singleton Events
- The SingletonPropertyEventArgs
- [Example Usage](#Example Usage)
- SingletonException
- SingletonAttribute
- SingletonManager - Managing Singletons
- Serialization / Deserialization
- Tests
- Notes
- Best practices
- Useful Links
Full Version | NuGet | Build | NuGet Install |
---|---|---|---|
CSharp.Portable-Singleton | PM> Install-Package CSharp.Portable-Singleton |
Please visit here for a complete reference, which is also included in the NuGet package.
- Install the package with the NuGet Package manager:
PM> Install-Package CSharp.Portable-Singleton
. - Add the namespace to your code:
using Core.Singleton;
. - Derive the supposed singleton class, elevating it to the logical singleton-class:
MySingleton : Singleton<MySingleton>
. - 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");
.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.
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.
- Accessing
Singleton<T>.CurrentInstance
orSingleton<T>.Instance
for the first time - Creating a new explicit instance:
new T()
- Using
SingletonAttribute
such as[Singleton]class T : Singleton<T>{...}
and subsequently callingInitialize()
from aSingletonmanager
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 MethodToSingleton()
e.g.typeof(MyClass).GetTypeInfo().ToSingleton()
- Please refer to the
Examples
for code and case scenarios
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.
- 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)
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 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 propertyValue
: the boxed current value of the propertyProperty
: the property encoded as an enum value ofSingletonProperty
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
}
};
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:
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.
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'
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,
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 totrue
if the is allowed to be disposedCreateInternal
(default=true): Set tofalse
if the Singleton is only supposed to be instantiated externally by explicit declaration within the user source-codeInitByAttribute
(default=true): Set totrue
to allow joint initialization by theSingletonManager
methodInitialize
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.
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));
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
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:
Should you be certain that you ran into a bug, please push a new issue here.
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>
For a more complex inheritance singleton scenario, please refer to README_Example_Advanced.md
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 avoidAClass.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