Email to Torsten Weber
Feed Icon
.NET User Group Leipzig

Durch einen Singleton-Provider bzw. einer -Base, die jeweils mit generischen Typen in C# 2.0 (Generics) implementiert werden, wird der Freiheitsgrad eines Softwarentwicklers stark eingeschränkt. Statt dieses Entwurfsmuster immer wieder pro Klasse neu zu implementieren, ist nur die einmalige Implementierung eines Singleton-Providers notwendig. Dieser ermöglicht dann die Anwendung auf (fast) jede Klasse. Das heißt konkret, dass mitttels SingletonProvider<MeineKlasse>.Instance.MethodeA() auf die MethodeA() im Sinne eines Singleton zugegriffen werden, ohne dass ein Singleton-Entwurfsmuster in der Klasse MeineKlasse überhaupt implementiert wurde.

Doch damit nicht genug. Der Freiheitsgrad kann weiter eingeschränkt werden. Und zwar indem der Singleton-Provider bzw. die -Base bei Bedarf einfach automatisch generiert oder sogar als Bibliothek eingebunden werden. Solche Techniken forciert insbesondere der Ansatz der Software Factories, wie er von Jack Greenfield und Keith Short definiert wurde. Grund genug für Microsoft, für die Entwicklung von und mit Software Factories auch entsprechend benötigte Tools wie das Guidance Automation- und das DSL-Toolkit (GAT) zur Verfügung zu stellen und in naher Zukunft durch weitere zu ergänzen.

Inwieweit mit den wichtigsten Konzepten wie Automatisierung (Guidance and Automation) oder auch der domänenspezifischen Modellierung (DSM) die Ziele von Software Factories erreicht werden kann, zeigt der Artikel "Industrialisierung der Wiederverwendung – Software Factories"¹, ².

Nachfolgend ist ein Singleton-Provider und eine -Base dargestellt, die jeweils getrennt herunterladbar sind. Ein Visual Studio 2005-Projekt gibt es hier: Singleton.zip (40,21 KB).

Singleton-Provider

Stellt ein Singleton-Muster zur Verfügung. Dabei auftretene Ausnahmen bei der Initialisierung werden gefangen und können abgefragt bzw. erneut geworfen werden.

using System;

namespace Patterns
{
    /// <summary>
    /// Stellt ein Singleton-Muster direkt zur Verfügung.
    /// </summary>
    /// <typeparam name="T">Der Typ, für den ein Singleton erstellt werden soll.</typeparam>
    public sealed class SingletonProvider<T> where T : new()
    {
        #region Classes
        /// <summary>
        /// Innere Klasse für Singleton-Pattern.
        /// </summary>
        private class SingletonCreator
        {
            /// <summary>
            /// Aufgetretene Exception im Initialisieren/Instanzieren.
            /// </summary>
            internal static readonly Exception s_InitializationException = null;
            /// <summary>
            /// Die Instanz.
            /// </summary>
            internal static readonly T s_Instance = default(T);

            /// <summary>
            /// Inititialisiert eine Instanz des Typs T.
            /// </summary>
            static SingletonCreator()
            {
                try
                {
                    s_Instance = new T();
                }
                catch (Exception exception)
                {
                    s_Instance = default(T);
                    s_InitializationException = exception.InnerException;
                }
            }
        }
        #endregion

        #region Properites
        /// <summary>
        /// Ruft die Exception ab, aufgrund deren die Instanziierung/Initialisierung fehlgeschlagen ist.
        /// </summary>
        public static Exception InitializationException
        {
            get { return SingletonCreator.s_InitializationException; }
        }

        /// <summary>
        /// Ruft die Instanz ab.
        /// </summary>
        public static T Instance
        {
            get { return SingletonCreator.s_Instance; }
        }
        #endregion

        #region SingletonProvider
        /// <summary>
        /// Initialisiert eine neue Instanz der SingletonProvider-Klasse.
        /// </summary>
        private SingletonProvider() { }
        #endregion

        #region Methods
        /// <summary>
        /// Prüft ob beim Initialisieren einer Instanz einer Klasse eine Exception aufgetreten ist.
        /// Wenn ja, wird diese Exception geworfen.
        /// </summary>
        public static void CheckInitialization()
        {
            if (SingletonProvider<T>.InitializationException != null)
            {
                throw SingletonProvider<T>.InitializationException;
            }
        }
        #endregion
    }
}

SingletonProvider.zip (1 KB)


Singleton-Base

Stellt ein Singleton-Muster über Vererbung zur Verfügung. Auftretene Ausnahmen bei der Initialisierung werden gefangen und können abgefragt bzw. erneut geworfen werden.

using System;
using System.Reflection;

namespace Patterns
{
    /// <summary>
    /// Stellt ein Singleton-Muster über Vererbung zur Verfügung.
    /// </summary>
    /// <typeparam name="T">Der Typ, für den ein Singleton erstellt werden soll.</typeparam>
    public class SingletonBase<T> where T : class
    {
        #region Classes
        /// <summary>
        /// Innere Klasse für Singleton-Pattern.
        /// </summary>
        private class SingletonCreator
        {
            /// <summary>
            /// Aufgetretene Exception im Initialisieren/Instanzieren.
            /// </summary>
            internal static readonly Exception s_InitializationException = null;
            /// <summary>
            /// Die Instanz.
            /// </summary>
            internal static readonly T s_Instance = default(T);

            /// <summary>
            /// Inititialisiert eine Instanz des Typs T.
            /// </summary>
            static SingletonCreator()
            {
                try
                {
                    Type typeT = typeof(T);
                    ConstructorInfo constructorInfo = null;

                    constructorInfo = typeT.GetConstructor(
                        BindingFlags.DeclaredOnly |
                        BindingFlags.Instance |
                        BindingFlags.NonPublic,
                        null,
                        Type.EmptyTypes,
                        null);

                    if (constructorInfo == null)
                    {
                        // falls kein privater Konstruktur vorhanden
                        constructorInfo = typeT.GetConstructor(
                            BindingFlags.DeclaredOnly |
                            BindingFlags.Instance |
                            BindingFlags.Public,
                            null,
                            Type.EmptyTypes,
                            null);
                    }

                    Object o = constructorInfo.Invoke(null);
                    s_Instance = o as T;
                }
                catch (Exception exception)
                {
                    s_Instance = default(T);
                    s_InitializationException = exception.InnerException;
                }
            }
        }
        #endregion

        #region Properites
        /// <summary>
        /// Ruft die Exception ab, aufgrund deren die Instanziierung/Initialisierung fehlgeschlagen ist.
        /// </summary>
        public static Exception InitializationException
        {
            get { return SingletonCreator.s_InitializationException; }
        }

        /// <summary>
        /// Ruft die Instanz ab.
        /// </summary>
        public static T Instance
        {
            get { return SingletonCreator.s_Instance; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Prüft ob beim Initialisieren einer Instanz einer Klasse eine Exception aufgetreten ist.
        /// Wenn ja, wird diese Exception geworfen.
        /// </summary>
        public static void CheckInitialization()
        {
            if (SingletonBase<T>.InitializationException != null)
            {
                throw SingletonBase<T>.InitializationException;
            }
        }
        #endregion
    }
}

SingletonBase.zip (1 KB)


Beispiel der Verwendung des Singleton-Providers

SingletonProvider.<TestClass>.Instance.WriteHelloWorldOnConsole();

...

class TestClass
{
   public void WriteHelloWorldOnConsole()
   {
      Console.WriteLine("Hello World");
   }
}

Und abschließend noch ein Dankeschön an Dirk, der mich mit seinem Blog-Eintrag Das Singleton - das Unbekannte Wesen inspiriert hat.

¹ OBJEKTspektrum 03/2006, Gunther Lenz, Torsten Weber, "Industrialisierung der Wiederverwendung – Software Factories"
² Mehr Informationen zu den Themen Singleton, domänenspezifischen Sprachen und Software Factories:

Thursday, June 14, 2007 4:04:38 PM (W. Europe Standard Time, UTC+01:00)
Also durch die Code Analysis haste den Code aber nicht laufen lassen... ;) Und ungarische (oder 'angedeutete' ungarische) Notation ist bei .NET auch nichr sehr verbreitet. Ansonsten nett. Ich würde aber eine DLL daraus machen und diese dann in der Console App referenzieren - das geht dann über den derzeit Deinem Sample noch anhaftenden Hello World-Charakter hinaus.
Thursday, June 14, 2007 4:05:11 PM (W. Europe Standard Time, UTC+01:00)
Antwort:
Warnung 18 CA1810 : Microsoft.Performance : Initialisieren Sie alle statischen Felder in Patterns.SingletonProvider`1+SingletonCreator, wenn diese Felder deklariert werden, und entfernen Sie den expliziten statischen Konstruktor. C:\Dokumente und Einstellungen\Torsten Weber\Desktop\Singleton\Patterns\SingletonProvider.cs 27 Patterns

Nun alle Meldungen sind irrelevant, weil der Singleton-Code ebend z. B. statische Member bedingt, die durch einen statischen Konstruktur initialisiert werden müssen. Klar, sonst würde man so etwas vielleicht nicht machen, hier geht es aber nicht anders. Gerade wenn man z. B. statt TypeLoad- oder anderer Exceptions bei Klasse.Instance.Methode() die ursprüngliche Exception erhalten will. Die restlichen Meldungen sind ähnlich zu verstehen.

Stimmt, der Code ist vielleicht nach den folgenden Empfehlungen geringfügig optimierungsbedürftig:
Design Guidelines for Class Library Developers
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconnetframeworkdesignguidelines.asp

"Offizielle" interne Guidelines:
http://blogs.msdn.com/brada/articles/361363.aspx
Torsten Weber
Thursday, June 14, 2007 4:06:01 PM (W. Europe Standard Time, UTC+01:00)
Was man noch einbauen könnte, wäre ne Sache zur Multithreading-Sicherheit. Dafür die Ctors einfach mit einem lock{} einschließen. Und danke, ich habe das default-Keyword (im Zusammenhang mit Generics) neu gelernt!
Übrigens könnte man die Singleton-Klassen noch static machen, was seit .NET 2.0 ja nun auch geht.

Aus MSDN:
A class can be declared static, indicating that it contains only static members. It is not possible to create instances of a static class using the new keyword. Static classes are loaded automatically by the .NET Framework common language runtime (CLR) when the program or namespace containing the class is loaded.
Der Unterschied ist, dass die CLR alle Singletons dann schon beim Laden der Assembly/Namespace instanziiert und nicht erst bei der ersten Nutzung eines Singletons. Und man kann keine Instanzmethoden hinzufügen (Compiler-Fehler). Das wären dann auch schon die Unterschiede. Ob das Sinn macht, hängt aber ganz vom Einsatzgebiet ab bzw. von den Kosten der Instanziierung/Speicherverbrauch des Singletons.
Thursday, June 14, 2007 4:06:35 PM (W. Europe Standard Time, UTC+01:00)
Hi Torsten, Coole Sache das :)
Danke und Viele Grüße.
Wednesday, February 06, 2008 3:00:15 PM (W. Europe Standard Time, UTC+01:00)
Hi Torsten,

könntest Du mir einen Tip geben, wie ich ich es schaffe, in der abgeleiteten Klasse eine Initialisierungsfunktion aufzurufen ?
Diese soll nur aufgerufen werden, wenn das Singleton (abgeleitet von SingletonBase) das erste mal erstellt wird.

mfg, Wolfgang
Wolfgang Maier
Wednesday, February 06, 2008 6:46:53 PM (W. Europe Standard Time, UTC+01:00)
Das ist etwas für den Konstruktor:

using System;

namespace ConsoleApplication {
class Program {
static void Main() {
Console.WriteLine(TestClass.Instance.InstanceVariable);
Console.WriteLine(TestClass.Instance.InstanceVariable);
Console.WriteLine(TestClass.Instance.InstanceVariable);
Console.Read(); } }

public class TestClass : Patterns.SingletonBase<TestClass> {
string instanceVariable = String.Empty;
public TestClass() { instanceVariable = DateTime.Now.ToString(); }
public String InstanceVariable { get { return instanceVariable; }
} } }
All comments require the approval of the site owner before being displayed.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, blockquote@cite, em, i, strike, strong, sub, sup, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Live Comment Preview

Boldness, risk‐taking and a little bit of craziness – lateral thinker Torsten Weber
Boldness, risk‐taking and a little bit of craziness – lateral thinker Torsten Weber

.NET User Group Leipzig

Categories

Calendar

<February 2012>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

Archive

My subscribed blogs

show all
show less
Blogs of good friends (as OPML)
More Blogs (as OPML)