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 |
|
Full Version | NuGet | Build | NuGet Install |
---|---|---|---|
CSharp.Portable-Singleton | PM> Install-Package CSharp.Portable-Singleton |
Social:
Please visit here for a complete reference, which is also included in the NuGet package.
PM> Install-Package CSharp.Portable-Singleton
.using Core.Singleton;
.MySingleton : Singleton<MySingleton>
.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.
Singleton<T>.CurrentInstance
or Singleton<T>.Instance
for the first timenew T()
SingletonAttribute
such as [Singleton]class T : Singleton<T>{...}
and subsequently calling Initialize()
from a Singletonmanager
instanceActivator.CreateInstance(typeof(T));
new T(...)
SingletonManager
(see below)TypeInfo
Extension Method ToSingleton()
e.g. typeof(MyClass).GetTypeInfo().ToSingleton()
Examples
for code and case scenariosThe generic Singleton<T>
construct has the following static properties, which are referenced in EnumSingletonProperty.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.
myobj is ISingleton
typeof(MyClass).GetTypeInfo().IsSingleton()
Respectively, omit the call to GetTypeInfo()
as shown above, if the comparison type is already a TypeInfo
instance.
(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 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
}
};
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 EnumSingletonCause.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 to true
if the is allowed to be disposedCreateInternal
(default=true): Set to false
if the Singleton is only supposed to be instantiated externally by explicit declaration within the user source-codeInitByAttribute
(default=true): Set to true
to allow joint initialization by the SingletonManager
method Initialize
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:
Fig 1:
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:
singleton<T>
: e.g. ParentOfParentOfAClass.Instance
is OK, but avoid AClass.Instance
SingletonAttribute