Kategorien

Navigation

Feeds/Meta

Blog RSS Feed
Kommentare RSS Feed

Manuel Josupeit-Walter - XING Profil
Manuel Josupeit-Walter - Amazon Wunschliste
Manuel Josupeit-Walter - XMPP (Jabber)
Manuel Josupeit-Walter - ICQ
Manuel Josupeit-Walter - Skype

Bloggeramt.de
BlogAlm
Add to Technorati Favorites

SpamPoison

josupeit.com > Weblog > Informatik und Technik > C# und .NET im Allgemeinen


C# 4.0 dynamic-Schlüsselwort

Datum:   09.12.2011, 20:56 Uhr
Kategorie:   C# und .NET im Allgemeinen Feed dieser Kategorie abonnieren
Kommentare:   0

Ich entwickle Software nun schon seit Version 1.1 des Frameworks in .net und mit jeder Version kommen mehr oder weniger bahnbrechende Sprachfeatures hinzu. Generics, Linq oder lokale Typinferenz sind nur einige Buzzwords, die mir auf die Schnelle in diesem Zusammenhang einfallen. Seit heute jedoch bin ich Fan eines weiteren Sprachfeatures, das unter anderem der Interoperabilität mit untypisierten Sprachen, wie Javascript geschuldet ist: Des dynamic-Schlüsselworts.

Mit Version 4.0 des .net-Frameworks hat Microsoft die sogenannte DLR, die Dynamic Runtime Language eingeführt und damit seine Sprachen um dynamische Typisierung erweitert. Bitte nicht falsch verstehen, Variablen, die als dynamic deklariert werden sind nicht etwa untypisiert, vielmehr geht der Compiler zunächst davon aus, dass jede beliebige Operation auf einem dynamic-Objekt ausgeführt werden kann. Etwaige Probleme entstehen erst zur Laufzeit (Duck Typing ist z.B. nicht "einfach so" möglich).

Während früher z.B. viel reflektiver Code notwendig war, um zur Laufzeit auf einem Objekt beliebigen Typs Methoden auszuführen, wird das Leben mit Hilfe des dynamic-Schlüsselwortes weitaus leichter. Das folgende Beispiel ist dem MSDN-Magazine entnommen: Dynamic .NET - Understanding the Dynamic Keyword in C# 4.0

object calc = GetCalculator();
Type calcType = calc.GetType();
object res = calcType.InvokeMember("Add", BindingFlags.InvokeMethod, null, new object[] { 10, 20 });
int sum = Convert.ToInt32(res);

Vereinfachung durch die Nutzung der dynamischen Typisierung:

dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

Interessanter finde ich jedoch, dass durch dieses Schlüsselwort auch dynamisches Dispatching ermöglicht wird! Das folgende Beispiel stellt eine Factory-Klasse dar, die verschiedene "Personen" instanziieren, und anhand mehrerer Methodenüberladungen einige Eigenschaften dieser "Personen" auf Standardwerte setzen soll. Dieses Beispiel funktioniert aufgrund des statischen Dispatchings jedoch nicht:

public static class PersonFactory {
  public static TPerson CreatePerson<TPerson>() where TPerson : Person {
    TPerson p = (TPerson)Activator.CreateInstance(typeof(TPerson), new object[] { });
    InitializePerson(p); // Wird immer die erste Überladung aufrufen
  }

  public static void InitializePerson(Person p) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Student s) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Teacher t) {
    t.Students = new List<Student>();
  }
}

public class Person {
  public string LastName { get; set; }
  public string FirstName { get; set; }
}

public class Student : Person {
  /* This is just a stub */
}

public class Teacher : Person {
  public IList<Student> Students { get; set; }
}

Da der statische Typ von p in der Methode CreatePerson der PersonFactory Person ist, wird immer die erste Überladung der InitializePerson-Methode aufgerufen werden. Das steht bereits zum Zeitpunkt der Kompilierung fest. Die einzige Möglichkeit bisher, die richte Überladung ohne dynamisches Dispatching aufzurufen, ist der Umweg über sog. Double Dispatching:

public static class PersonFactory {
  public static TPerson CreatePerson<TPerson>() where TPerson : Person {
    TPerson p = (TPerson)Activator.CreateInstance(typeof(TPerson), new object[] { });
    p.InitializePerson();
  }

  public static void InitializePerson(Person p) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Student s) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Teacher t) {
    t.Students = new List<Student>();
  }
}

public abstract class Person {
  public string LastName { get; set; }
  public string FirstName { get; set; }

  public abstract void InitializePerson();
}

public class Student : Person {
   public override void InitializePerson() {
    PersonFactory.InitializePerson(this);
  }
}

public class Teacher : Person {
  public IList<Student> Students { get; set; }

   public override void InitializePerson() {
    PersonFactory.InitializePerson(this);
  }
}

Durch verwendung des doppelten Dispatchings wird nun die korrekte Überladung der InitializePerson-Methode innerhalb der PersonFactory aufgerufen, allerdings muss ab jetzt jede von Person erbende Klasse die Methode InitializePerson mit immer dem selben Aufruf implementieren, was zu redundantem Code führt, der darüber hinaus auch noch leicht vergessen werden kann und dessen Sinn sich für Laien oft nicht erschließt. Sollte künftig jedoch eine weitere Vererbung hinzukommen, wie z.B. der SuperTeacher, der die Klasse Teacher erweitert und nicht mehr vom Compiler gezwungen wird, die Methode InitializePerson zu überschreiben, wird wiederum immer nur die Methodenüberladung für Teacher aufgerufen werden, bis die Methode auch von SuperTeacher überschrieben wird. Der Code in der Methode ist jedoch der selbe, wie der der Basisklasse Teacher. :-(

Die Lösung für dieses Dilemma ist das dynamic-Schlüsselwort, denn dadurch wird erst zur Laufzeit und nicht bereits bei der Kompilierung entschieden, welche Methode aufgerufen wird:

public static class PersonFactory {
  public static TPerson CreatePerson<TPerson>() where TPerson : Person {
    dynamic p = Activator.CreateInstance(typeof(TPerson), new object[] { });
    InitializePerson(p); // Wird die beste Überladung aufrufen!!
  }

  public static void InitializePerson(Person p) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Student s) {
    /* Do sth. useful here */
  }

  public static void InitializePerson(Teacher t) {
    t.Students = new List<Student>();
  }
}

public class Person {
  public string LastName { get; set; }
  public string FirstName { get; set; }
}

public class Student : Person {
  /* This is just a stub */
}

public class Teacher : Person {
  public IList<Student> Students { get; set; }
}

Fertig. Geil! 8-)

Visual Studio 2008 auf Vista x64

Datum:   03.10.2008, 15:23 Uhr
Kategorie:   C# und .NET im Allgemeinen Feed dieser Kategorie abonnieren
Kommentare:   0

Seit geraumer Zeit bin ich endlich im Besitz meines neuen Rechners, allerdings bescherte mir die Installation von Visual Studio 2008 auf der 64-Bit Version von Windows Vista so einige Probleme, die auch hier bereits im Februar gebloggt wurden. (Leider bin ich trotz emsigen Googlens nicht auf eine solche Problemlösung gestoßen.)

Trotz einer frischen Installation von Windows und allen nötigen Treibern scheiterte die Installation von Visual Studio bereits beim .NET Framework 3.5 mit einer ganzen Reihe roter Kreuze. Zudem erfreute ich mich seither daran, dass mein neuer Rechner nun ca. 10 Minuten (!!) zum booten brauchte, bis ich letztlich meinen Desktop vor mir sah (nicht, dass ich jetzt in der Lage gewesen wäre, den PC in irgend einer Form zu bedienen :-)).

Die Lösung des Problemes jedoch heißt Service Pack 1 für Windows Vista x64, dass Microsoft glücklicherweise online zum Download bereitstellt. Schade allerdings, dass dort explizit vom Download abgeraten wird, da die Updates in Vista automatisch erfolgten -- Leider wurde, jedenfalls bei mir, dieses Service Pack nicht per Auto-Update ausgeliefert. Vielen Dank, Mr. Ballmer, das bescherte mir 10 Stunden Arbeit...

Systemweiter Singleton in C#: Nachtrag

Datum:   16.05.2008, 13:29 Uhr
Kategorie:   C# und .NET im Allgemeinen Feed dieser Kategorie abonnieren
Kommentare:   0

Vor einiger Zeit berichtete ich hier über die Implementierung eines systemweiten Singleton in C#. Trotz Freigabe des registrierten Mutexes bei Terminierung des Hauptprozesses und damit einhergehender Vernichtung der über die Anwendungsdomäne hinaus bereitgestellten Objekte, wurde bei Aufruf der GetInstance-Methode kein neues Objekt erzeugt (vielen Dank für den Hinweis, Tom).

Diesem Problem habe ich etwas Zeit gewidmet und eine Lösung zum Download bereitgestellt.

Systemweiter Singleton in C#

Datum:   19.12.2007, 15:51 Uhr
Kategorie:   C# und .NET im Allgemeinen Feed dieser Kategorie abonnieren
Kommentare:   4

Am 21. November, also vor gut einem Monat habe ich in meinem Artikel Singleton als vererbte Klasse am Beispiel C# gezeigt, wie das Singleton Entwurfsmuster so implementiert werden kann, dass eine Klasse lediglich von einer Singleton-Oberklasse erben muss, um nicht mehr als einmal in der aktuellen Applikationsdomäne instanziiert werden zu können. Wie versprochen möchte ich die damals Schritt für Schritt entwickelte Klasse heute so ausbauen, dass ein systemweiter Singleton realisiert werden kann. Dadurch kann von einer Klasse über die Applikationsdomäne hinaus nur ausschließlich eine Instanz existieren und wird es sehr einfach möglich, dass eine Anwendung beispielsweise nur einmal gestartet werden kann und jeder weitere Start lediglich das Hauptfenster in den Vordergrund holt.

Damit allerdings zwei Prozesse eines Systemes miteinander kommunizieren können, wird ein Datenkanal benötigt, über den dann ein Prozess von einem anderen ferngesteuert werden kann. Bitte behalten Sie also die folgende Illustration während des lesens im Hinterkopf, es wird Ihnen das Leben etwas erleichtern: 

 

Remoting Illustration

 

Bevor ich nun beginne, möchte ich kurz den letzten Stand in Erinnerung rufen: Wir entwickelten eine SingletonProvider Klasse, die alle Instanzen im eigentlichen Sinne verwaltet hat. Eine Vererbung fand im Anschluss daran über die Klasse SingletonBase statt:


SingletonProvider

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;

public static class SingletonProvider
{
  private static Hashtable Selfs = new Hashtable();
  private static object Lock     = new Object();

  public static T GetInstance<T>(out bool New)
    where T: class // Jede Klasse ist erlaubt
  {
    // Auf öffentlichen Konstruktor prüfen
    ConstructorInfo checkCtor = (typeof(T)).GetConstructor(Type.EmptyTypes);
   
    /*
     * Falls es einen solchen Konstruktor gibt,
     * schmeißen wir einen Fehler, da es dem
     * Charakter eines Singleton widerspricht.
     */

    if (checkCtor != null)
      throw new InvalidOperationException("Singleton means that you don't have any public constructors");

    // Threadsynchronisation
    lock (Lock) {
      if (Selfs.ContainsKey(typeof(T).GUID))
      {
        // Instanz existiert bereits
        New = false;
        return (T)Selfs[typeof(T).GUID];
      }

      // Neue Instanz über Reflektion erstellen
      ConstructorInfo ctorInfo;
     
      // Geschützte Konstruktoren auslesen
      ctorInfo = typeof(T).GetConstructor(
                  BindingFlags.NonPublic |
                  BindingFlags.Instance,
                  null,
                  Type.EmptyTypes,
                  null
                );
               
      // Konstruktor ohne Parameter aufrufen
      T _Instanz = (T)ctorInfo.Invoke(new object[] { });
     
      // Instanz der Hashtabelle zuführen
      Selfs.Add(typeof(T).GUID, _Instanz);
      New = true;
     
      return (T)Selfs[typeof(T).GUID];
    }
  }
 
  public static T GetInstance<T>()
    where T: class
  {
    bool _Trash;
    return GetInstance<T>(out _Trash);
  }
}

SingletonBase

public abstract class SingletonBase<T>
  where T: SingletonBase<T>
{
  public static T GetInstance(out bool New)
  {
    return SingletonProvider.GetInstance<T>(out New);
  }
 
  public static T GetInstance()
  {
    bool _Trash;
    return GetInstance(out _Trash);
  }
}

Um nun zunächst die SingletonProvider-Klasse auszubauen, verwende ich einen sogenannten Mutex1. Mutexe werden in aller Regel zur Prozesssynchronisation verwendet, das heißt, um sicherzustellen, dass kritische Programmabschnitte nicht gleichzeitig ausgeführt werden können, um beispielsweise Dateninkonsistenzen zu vermeiden. Sie lassen sich allerdings auch für unseren Zweck nutzen, da Mutexe systemweit registriert werden. Da sich die nötigen Klassen für Mutexe und unser folgendes Vorhaben aber in anderen Namensräumen (und Assemblies) befinden, müssen zunächst ein Verweis auf die Assembly System.Runtime.Remoting gesetzt - und die bekannten using-Direktiven um einige Namensräume ergänzt werden, so dass unsere Klassendefinition GlobalSingletonProvider künftig von den folgenden Zeilen angeführt wird:
 

using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Channels;
using System.Runtime.Serialization.Formatters;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Runtime.Serialization;
using System.Reflection;

public static class GlobalSingletonProvider
{ ... }


Dadurch aber müssen für jedes Singleton-Objekt nicht nur Instanzen verwaltet werden, sondern auch noch die zugehörigen Mutexe, die beim System registriert wurden. Hierzu wird unsere Liste von Deklarationen um eine Hashtabelle erweitert.

public static class GlobalSingletonProvider
{
  private static Hashtable Selfs              = new Hashtable();
  private static Hashtable ApplicationMutexes = new Hashtable();
  private static object Lock                  = new Object();

  // ...
}


Im Gegensatz zum herkömmlichen SingletonProvider, wird im Rupf der Methode GetInstance() nun direkt nach der Überprüfung, ob bereits in der aktuellen Applikationsdomäne eine Instanz der Klasse existiert, ein neuer Mutex registriert, der als Namen die GUID, also eine absolut eindeutige ID der zu instanziierenden Klasse trägt. Eine boolsche Variable New gibt hier an, ob dem System ein solcher Mutex bereits bekannt ist. Das nämlich ist genau dann der Fall, wenn bereits außerhalb der Applikationsdomäne eine Instanz erstellt worden ist; der dort registrierte Mutex trägt nämlich aufgrund der ID der Klasse den selben Namen. Jetzt wird über sogenannte Ipc Channels ein Remote-Proxy auf dieses Objekt geholt, so dass das bestehende Objekt quasi "ferngesteuert" werden kann. Als Resultat dieser Hintergrundarbeit können Sie auf dem Objekt arbeiten, als hätten Sie es in Ihrer Applikationsdomäne erzeugt (Achtung: Das heißt nicht, dass sie auf dem Objekt arbeiten können, als hätten Sie es im gleichen Thread erzeugt. Beachten Sie Threadübergreifende Vorgänge!), alle Aufrufe werden in Wahrheit allerdings transparent weitergereicht. Kann im Gegensatz dazu der Mutex beim System neu registriert werden, lässt das darauf schließen, dass es sich um eine neue Instanz der Klasse handelt. Auch in diesem Fall wird ein Ipc Kanal bereitgestellt und darüber das Objekt verfügbar gemacht. Dieses Vorgehen bezeichnet man häufig auch als Marshalling2. Vorraussetzung für das Marshallen von Objekten ist allerings, dass diese Objekte von der Klasse MarshalByRefObject erben.

Das folgende Listing zeigt nun die bisherigen Modifikationen an der herkömmlichen SingletonProvider-Klasse, bereits bekannte Zeilen sind hier allerdings ausgegraut, damit deutlich wird, welche neuen Anweisungen unserem Vorhaben dienlich sind:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Channels;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Remoting;
using System.Runtime.Serialization;

public static class GlobalSingletonProvider
{
  private static Hashtable Selfs              = new Hashtable();
  private static Hashtable ApplicationMutexes = new Hashtable();
  private static object Lock                  = new Object();

  public static T GetInstance<T>(out bool New)
    where T:
MarshalByRefObjekt // Jede Klasse ist erlaubt, die von MarshalByRefObjekt erbt
  {
    // Auf öffentlichen Konstruktor prüfen
    ConstructorInfo checkCtor = (typeof(T)).GetConstructor(Type.EmptyTypes);
   
    /*
     * Falls es einen solchen Konstruktor gibt,
     * schmeißen wir einen Fehler, da es dem
     * Charakter eines Singleton widerspricht.
     */
    if (checkCtor != null)
      throw new InvalidOperationException("Singleton means that you don't have any public constructors");

    // Threadsynchronisation
    lock (Lock) {
      if (Selfs.ContainsKey(typeof(T).GUID))
      {
        // Instanz existiert bereits
        New = false;
        return (T)Selfs[typeof(T).GUID];
      }


      // Mittels Mutex überprüfen, ob bereits eine Instanz existiert
      ApplicationMutexes.Add(typeof(T).GUID, new Mutex(true, typeof(T).GUID.ToString(), out New));

      // Ipc Kanal deklarieren
      IpcChannel _Channel;

      if (!New)
      {
        /*
         * Falls in einer anderen Applikationsdomäne eine Instanz existiert,
         * muss diese nun als Remote-Proxy bereitgestellt werden, dazu
         * wird zunächst ein Ipc Channel registriert.
         */


        SoapServerFormatterSinkProvider _ServProv = new SoapServerFormatterSinkProvider();
        _ServProv.TypeFilterLevel = TypeFilterLevel.Full;

        SoapClientFormatterSinkProvider _ClientProv = new SoapClientFormatterSinkProvider();

        // Zur Serialisierung wird der Soap-Formatter verwendet
        _Channel = new IpcChannel(null, _ClientProv, _ServProv);
        ChannelServices.RegisterChannel(_Channel, false);

        // Nun wird die Instanz über diesen Kanal geholt
        T _Inst = (T)Activator.GetObject(typeof(T), "ipc://localhost:9190/GlobalSingleton_" + typeof(T).GUID.ToString());
        Selfs.Add(typeof(T).GUID, _Inst);
      }
      else
      {
        /*
         * Es existiert keine Instanz, also wird eine neue Instanz erstellt,
         * der Mutex wird bei Applikationsende wieder freigegeben
         */


        AppDomain.CurrentDomain.ProcessExit += delegate(object sender, EventArgs e)
        {
          try
          {
            ((Mutex)ApplicationMutexes[typeof(T).GUID]).ReleaseMutex();
          }
          catch { /* Fehler ignorieren */ }
        };

        // Neue Instanz über Reflektion erstellen
        ConstructorInfo ctorInfo;
     
        // Geschützte Konstruktoren auslesen
        ctorInfo = typeof(T).GetConstructor(
                    BindingFlags.NonPublic |
                    BindingFlags.Instance,
                    null,
                    Type.EmptyTypes,
                    null
                  );
               
        // Konstruktor ohne Parameter aufrufen
        T _Instanz = (T)ctorInfo.Invoke(new object[] { });
     
        // Instanz der Hashtabelle zuführen
        Selfs.Add(typeof(T).GUID, _Instanz);

        // Ipc Kanal registrieren
        SoapServerFormatterSinkProvider _ServProv = new SoapServerFormatterSinkProvider();
        _ServProv.TypeFilterLevel = TypeFilterLevel.Full;

        SoapClientFormatterSinkProvider _ClientProv = new SoapClientFormatterSinkProvider();

        // Einstellungen für den Kanal werden in einer Hashtabelle abgelegt
        IDictionary _Hashtable = new Hashtable();
        _Hashtable["portName"] = "localhost:9190";

        // Zur Serialisierung wird der Soap-Formatter verwendet
        _Channel = new IpcChannel(_Hashtable, _ClientProv, _ServProv);
        ChannelServices.RegisterChannel(_Channel, false);

        // Objekt via Ipc Kanal "marshallen"
        RemotingServices.Marshal((MarshalByRefObject)Selfs[typeof(T).GUID], "GlobalSingleton_" + typeof(T).GUID.ToString());
      }

      return (T)Selfs[typeof(T).GUID];
    }
  }
 
  public static T GetInstance<T>()
    where T:
MarshalByRefObject
  {
    bool _Trash;
    return GetInstance<T>(out _Trash);
  }
}


Die Klasse GlobalSingletonBase unterscheidet sich kaum von der Klasse SingletonBase, mit Ausnahme, dass die neue Klasse nun von MarshalByRefObject erbt, so dass schlussendlich folgender Quelltext genau die Funktionalität bietet, die in der Einleitung erwähnt wurde:

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Channels;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Remoting;
using System.Runtime.Serialization;

public static class GlobalSingletonProvider
{
  private static Hashtable Selfs              = new Hashtable();
  private static Hashtable ApplicationMutexes = new Hashtable();
  private static object Lock                  = new Object();

  public static T GetInstance<T>(out bool New)
    where T: MarshalByRefObjekt // Jede Klasse ist erlaubt, die von MarshalByRefObjekt erbt
  {
    // Auf öffentlichen Konstruktor prüfen
    ConstructorInfo checkCtor = (typeof(T)).GetConstructor(Type.EmptyTypes);
   
    /*
     * Falls es einen solchen Konstruktor gibt,
     * schmeißen wir einen Fehler, da es dem
     * Charakter eines Singleton widerspricht.
     */

    if (checkCtor != null)
      throw new InvalidOperationException("Singleton means that you don't have any public constructors");

    // Threadsynchronisation
    lock (Lock) {
      if (Selfs.ContainsKey(typeof(T).GUID))
      {
        // Instanz existiert bereits
        New = false;
        return (T)Selfs[typeof(T).GUID];
      }

      // Mittels Mutex überprüfen, ob bereits eine Instanz existiert
      ApplicationMutexes.Add(typeof(T).GUID, new Mutex(true, typeof(T).GUID.ToString(), out New));

      // Ipc Kanal deklarieren
      IpcChannel _Channel;

      if (!New)
      {
        /*
         * Falls in einer anderen Applikationsdomäne eine Instanz existiert,
         * muss diese nun als Remote-Proxy bereitgestellt werden, dazu
         * wird zunächst ein Ipc Channel registriert.
         */


        SoapServerFormatterSinkProvider _ServProv = new SoapServerFormatterSinkProvider();
        _ServProv.TypeFilterLevel = TypeFilterLevel.Full;

        SoapClientFormatterSinkProvider _ClientProv = new SoapClientFormatterSinkProvider();

        // Zur Serialisierung wird der Soap-Formatter verwendet
        _Channel = new IpcChannel(null, _ClientProv, _ServProv);
        ChannelServices.RegisterChannel(_Channel, false);

        // Nun wird die Instanz über diesen Kanal geholt
        T _Inst = (T)Activator.GetObject(typeof(T), "ipc://localhost:9190/GlobalSingleton_" + typeof(T).GUID.ToString());
        Selfs.Add(typeof(T).GUID, _Inst);
      }
      else
      {
        /*
         * Es existiert keine Instanz, also wird eine neue Instanz erstellt,
         * der Mutex wird bei Applikationsende wieder freigegeben
         */


        AppDomain.CurrentDomain.ProcessExit += delegate(object sender, EventArgs e)
        {
          try
          {
            ((Mutex)ApplicationMutexes[typeof(T).GUID]).ReleaseMutex();
          }
          catch { /* Fehler ignorieren */ }
        };

        // Neue Instanz über Reflektion erstellen
        ConstructorInfo ctorInfo;
     
        // Geschützte Konstruktoren auslesen
        ctorInfo = typeof(T).GetConstructor(
                    BindingFlags.NonPublic |
                    BindingFlags.Instance,
                    null,
                    Type.EmptyTypes,
                    null
                  );
               
        // Konstruktor ohne Parameter aufrufen
        T _Instanz = (T)ctorInfo.Invoke(new object[] { });
     
        // Instanz der Hashtabelle zuführen
        Selfs.Add(typeof(T).GUID, _Instanz);

        // Ipc Kanal registrieren
        SoapServerFormatterSinkProvider _ServProv = new SoapServerFormatterSinkProvider();
        _ServProv.TypeFilterLevel = TypeFilterLevel.Full;

        SoapClientFormatterSinkProvider _ClientProv = new SoapClientFormatterSinkProvider();

        // Einstellungen für den Kanal werden in einer Hashtabelle abgelegt
        IDictionary _Hashtable = new Hashtable();
        _Hashtable["portName"] = "localhost:9190";

        // Zur Serialisierung wird der Soap-Formatter verwendet
        _Channel = new IpcChannel(_Hashtable, _ClientProv, _ServProv);
        ChannelServices.RegisterChannel(_Channel, false);

        // Objekt via Ipc Kanal "marshallen"
        RemotingServices.Marshal((MarshalByRefObject)Selfs[typeof(T).GUID], "GlobalSingleton_" + typeof(T).GUID.ToString());
      }

      return (T)Selfs[typeof(T).GUID];
    }
  }
 
  public static T GetInstance<T>()
    where T: MarshalByRefObject
  {
    bool _Trash;
    return GetInstance<T>(out _Trash);
  }
}


public abstract class GlobalSingletonBase<T> : MarshalByRefObject
  where T: GlobalSingletonBase<T>
{
  public static T GetInstance(out bool New)
  {
    return GlobalSingletonProvider.GetInstance<T>(out New);
  }
 
  public static T GetInstance()
  {
    bool _Trash;
    return GetInstance(out _Trash);
  }
}

 
Auf diesem Weg kann nun über Vererbung ein systemweiter Singleton genutzt werden. Das folgende Beispiel zeigt die Verwendung in der Praxis:

public class Start : GlobalSingletonBase<Start>
{
    public Form WinForm;

    private Start()
    { }

    [STAThread]
    static void Main(string[] args)
    {
      bool Created;
      Start MainProcedure = Start.GetInstance(out Created);

      // Applikation starten, falls es sich um die erste Instanz handelt
      if (Created)
      {
        MainProcedure.WinForm = new HauptFormular();
        MainProcedure.WinForm.Show();
        Application.Run();
      }
      else
      {
        // Applikation läuft bereits, also in den Vordergrund holen
        MainProcedure.WinForm.WindowState = FormWindowState.Maximized;
        SetForegroundWindow(MainProcedure.WinForm.Handle);
      }
    }

    [DllImport("user32.dll")]
    public static extern bool SetForegroundWindow(IntPtr Handle);
}

public class HauptFormular : System.Windows.Forms.Form
{
    // ...
}


An dieser Stelle möchte ich mich für's lesen bedanken und hoffe, Ihnen an der ein oder anderen Stelle weitergeholfen zu haben. Dennoch bin ich mir bewusst darüber, dass einfach viele Dinge in diesem Artikel als bekannt vorrausgesetzt werden, dafür möchte ich mich entschuldigen. Sicher haben Sie Verständnis dafür, dass es sich nur um einen kurzen Abriss dessen handelt, was möglich ist und nicht um ein Handbuch und ich hoffe, dass bei Unklarheiten die Codebeispiele selbsterklärend sind. Ohne Programmierkenntnisse fällt es sicherlich schwer, alle Gedanken auf anhieb zu verstehen, ich kann Ihnen daher nur empfehlen, diese Beispiele schrittweise nachzuvollziehen.

 


GPLv3
1 Mutex: Wikipedia
2 Marshalling: Wikipedia

Implementierung eines Singleton in C#

Datum:   21.11.2007, 09:59 Uhr
Kategorie:   C# und .NET im Allgemeinen Feed dieser Kategorie abonnieren
Kommentare:   2

Es gibt Situationen, in denen man als Entwickler sicherstellen möchte, dass von einer Klasse nur ein Objekt instanziiert werden kann. Für diesen Anwendungsfalls gibt es bereits einen Lösungsansatz: Das Singleton-Pattern1. Das Prinzip dieses Ansatzes ist recht simpel und beruht unter anderem auf der Nutzung statischer Felder und Methoden. Diese sogenannten statischen Member einer Klasse unterscheiden sich von den Instanzmembern in sofern, als dass sie genutzt werden können, ohne explizit ein Objekt zu instanziieren.

Am Beispiel C# (Mircosoft .NET Framework 2.0) möchte ich an dieser Stelle in zwei Schritten eine Art systemweiten Singleton implementieren, dieser Artikel befasst sich allerdings zunächst mit dem ersten Schritt: der Implementierung des herkömmlichen Singleton. Die Erweiterung einer bestehenden Klasse zum Singleton funktioniert in C# wie folgt:


public class Beispiel
{
  /*
   * Dieses statische Feld hält unsere Instanz der
   * Klasse
   */
  private static Beispiel _Instanz;
 
  /*
   * Konstruktor der Klasse wird über den Zugriffsmodifizierer
   * "private" geschützt, um eine Instanziierung der Klasse von
   * außen zu unterbinden.
   */
  private Beispiel()
  {
    // TODO: Implementieren Sie Ihren Konstruktor hier
  }
 
  /*
   * Statische Methode, die die Instanz der Klasse zurück gibt.
   */
  public static Beispiel GetInstance()
  {
   if (_Instanz == null)
    _Instanz = new Beispiel();

   return _Instanz;
  }
}

Anhand dieses Beispiels sehen Sie, dass nur eine Instanz der Klasse Beispiel erzeugt wird und zwar über die statische Methode GetInstance():


public static void main(string[] args)
{
 Beispiel BeispielInstanz = Beispiel.GetInstance();
}

Der direkte Aufruf von BeispielInstanz = new Beispiel(); ist unzulässig, da der Konstruktor der Klasse privat ist.

Um auf einfachem Wege ihren Quelltext wiederverwenden zu können, lagern wir nun diesen Code in eine Bibliothek aus. Dieses Vorhaben allerdings erscheint nur auf den ersten Blick einfach: über Vererbung. Allerdings kann aus der Basisklasse nicht ohne Weiteres auf die abgeleitete Klasse zugegriffen werden, dies widerspräche auch dem Sinn von Vererbung, so dass folgendes Vorhaben nicht funktioniert:


public abstract class Singleton
{
  private static Singleton _Instanz;
 
  private Singleton()
  {
    // TODO: Implementieren Sie Ihren Konstruktor hier
  }
 
  public static Singleton GetInstance()
  {
   if (_Instanz == null)
     // Hier müsste die Kindklasse instanziiert werden
    _Instanz = new ?();

   return _Instanz;
  }
}

Abgesehen von diesem Problem kann eine abgeleitete Klasse so nicht instanziiert werden, da die Basisklasse durch die Verwendung des Schlüsselwortes private den Konstruktor schützt, ein Zugriff auf diesen Konstruktor durch Reflektion über die Basisklasse ist allerdings auch nicht möglich2.

Das Zauberwort an dieser Stelle lautet Generika3, denn generische Klassen erlauben es dem Entwickler, Typdefinitionen für ganze Klassen oder Methoden offen zu lassen. Wir entwickeln unser Singleton-Pattern also weiter zu einer statischen Klasse, also einer Klasse, die selbst nicht instanziiert werden kann, die aber unsere Singleton-Objekte verwaltet. Diese Klasse soll den Namen SingletonProvider bekommen. Damit künftig einfach entschieden werden kann, ob es sich um eine neue Instanz handelt oder nicht, erweitern wir gleich unsere Methode GetInstance() um einen boolschen Wert:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;

public static class SingletonProvider
{
  /*
   * Die Hashtabelle Selfs hält unsere Instanz-
   * objekte intern im Speicher.
   */

  private static Hashtable Selfs = new Hashtable();
 
  // Dieses Objekt wird zur Threadsynchronisation verwendet
  private static object Lock     = new Object();

  public static T GetInstance<T>(out bool New)
    where T: new()
  {
    // Threadsynchronisation
    lock (Lock) {
      /*
       * Überprüfen, ob bereits eine Instanz
       * des generischen Typen T existiert.
       * Als Schlüssel für die Hashtabelle verwenden
       * wir die GUID des Typen.
       */

      if (Selfs.ContainsKey(typeof(T).GUID))
      {
        // Instanz existiert bereits
        New = false;
        return (T)Selfs[typeof(T).GUID];
      }

      // Neue Instanz erstellen
      Selfs.Add(typeof(T).GUID, new T());
      New = true;
     
      return (T)Selfs[typeof(T).GUID];
    }
  }
}

Durch diese Klasse können nun auf einfachem Wege Singleton-Objekte erzeugt werden:


public static void main(string[] args)
{
 bool neueInstanz;
 Beispiel MeinBeispiel = SingletonProvider.GetInstance<Beispiel>(out neueInstanz);
}

Die Variable MeinBeispiel enthält nun eine neue Instanz der Klasse Beispiel, die Variable neueInstanz ist wahr, falls der SingletonProvider ein neues Objekt erzeugt hat. Der Typ Beispiel ist genau der "Lückenfüller" für unseren generischen Typen T im Beispiel oben. Einzige Bedingung: Es muss sich um eine Klasse handeln, die einen parameterlosen, öffentlichen Konstruktor besitzt (siehe where T: new()). Dies allerdings widerspricht wiederum dem Singleton-Gedanken, denn nun kann wieder eine Instanz der Klasse von Außen erzeugt werden: MeinBeispiel = new Beispiel(); Der Singleton ist somit ausgehebelt.

Wir modifizieren an dieser Stelle unseren SingletonProvider, so dass ausschließlich Klassen mit geschützten Konstruktoren verwendet werden können. Außerdem überladen wir unsere GetInstance-Methode der Einfachheit halber wie folgt:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;

public static class SingletonProvider
{
  private static Hashtable Selfs = new Hashtable();
  private static object Lock     = new Object();

  public static T GetInstance<T>(out bool New)
    where T: class // Jede Klasse ist erlaubt
  {
    // Threadsynchronisation
    lock (Lock) {
      if (Selfs.ContainsKey(typeof(T).GUID))
      {
        // Instanz existiert bereits
        New = false;
        return (T)Selfs[typeof(T).GUID];
      }

      // Neue Instanz über Reflektion erstellen
      ConstructorInfo ctorInfo;
     
      // Geschützte Konstruktoren auslesen
      ctorInfo = typeof(T).GetConstructor(
                  BindingFlags.NonPublic |
                  BindingFlags.Instance,
                  null,
                  Type.EmptyTypes,
                  null
                );
               
      // Konstruktor ohne Parameter aufrufen
      T _Instanz = (T)ctorInfo.Invoke(new object[] { });
     
      // Instanz der Hashtabelle zuführen
      Selfs.Add(typeof(T).GUID, _Instanz);
      New = true;
     
      return (T)Selfs[typeof(T).GUID];
    }
  }
 
  /*
   * Für den Fall, dass es egal ist,
   * ob es sich um ein neues Objekt handelt
   */

  public static T GetInstance<T>()
    where T: class
  {
    bool _Trash;
    return GetInstance<T>(out _Trash);
  }
}

Nun wird der private parameterlose Konstruktor zur Instanziierung verwendet. Als nächstes verbieten wir ausdrücklich öffentliche Konstruktoren und schmeißen andernfalls einen Fehler:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;

public static class SingletonProvider
{
  private static Hashtable Selfs = new Hashtable();
  private static object Lock     = new Object();

  public static T GetInstance<T>(out bool New)
    where T: class // Jede Klasse ist erlaubt
  {
    // Auf öffentlichen Konstruktor prüfen
    ConstructorInfo checkCtor = (typeof(T)).GetConstructor(Type.EmptyTypes);
   
    /*
     * Falls es einen solchen Konstruktor gibt,
     * schmeißen wir einen Fehler, da es dem
     * Charakter eines Singleton widerspricht.
     */

    if (checkCtor != null)
      throw new InvalidOperationException("Singleton means that you don't have any public constructors");

    // Threadsynchronisation
    lock (Lock) {
      if (Selfs.ContainsKey(typeof(T).GUID))
      {
        // Instanz existiert bereits
        New = false;
        return (T)Selfs[typeof(T).GUID];
      }

      // Neue Instanz über Reflektion erstellen
      ConstructorInfo ctorInfo;
     
      // Geschützte Konstruktoren auslesen
      ctorInfo = typeof(T).GetConstructor(
                  BindingFlags.NonPublic |
                  BindingFlags.Instance,
                  null,
                  Type.EmptyTypes,
                  null
                );
               
      // Konstruktor ohne Parameter aufrufen
      T _Instanz = (T)ctorInfo.Invoke(new object[] { });
     
      // Instanz der Hashtabelle zuführen
      Selfs.Add(typeof(T).GUID, _Instanz);
      New = true;
     
      return (T)Selfs[typeof(T).GUID];
    }
  }
 
  public static T GetInstance<T>()
    where T: class
  {
    bool _Trash;
    return GetInstance<T>(out _Trash);
  }
}

Mit Hilfe der obigen Implementierung der SingletonProvider-Klasse lassen sich nun beliebige Singleton-Objekte verwalten:


public class Beispiel
{

 private Beispiel()
 {
  Console.WriteLine("Klasse instanziiert");
 }
 
 public static void main(string[] args)
 {
  Beispiel MeinBeispiel  = SingletonProvider<Beispiel>.GetInstance();
  Beispiel MeinBeispiel2 = SingletonProvider<Beispiel>.GetInstance();
 }
}

Dieses Beispiel erzeugt auf der Konsole einmalig die Ausgabe "Klasse instanziiert", da der erste Aufruf von GetInstance() eine Instanz erzeugt. Der zweite Aufruf hingegen liefert ganz im Sinne des Singleton das selbe Objekt. Eine Instanz von Außen über MeinBeispiel = new Beispiel(); ist durch den geschützten Konstruktor übrigens nicht mehr möglich.

Möchte man nun eine Klasse schreiben, um andere Klassen von dieser "Singleton-Basis" erben zu lassen, bedienen wir uns der oben bereits implementierten Klasse SingletonProvider, der Singleton selbst soll hier allerdings nicht instanziiert werden und ist deshalb abstrakt:


public abstract class SingletonBase<T>
  where T: SingletonBase<T>
{
  public static T GetInstance(out bool New)
  {
    return SingletonProvider.GetInstance<T>(out New);
  }
 
  public static T GetInstance()
  {
    bool _Trash;
    return GetInstance(out _Trash);
  }
}

Da, wie oben bereits gesagt, die Elternklasse allerdings nicht auf die Kindklasse schließen kann, werwenden wir für die Anweisung where T: SingletonBase<T>, um den Typen zu "ermitteln". Dies ist zwar in gewisser Hinsicht eine Redundanz, da eigentlich bereits durch die Vererbung klar ist, dass eine Instanz von "Beispiel" erzeugt werden soll, leider gibt es meiner Ansicht nach aber derzeit keine wesentlich elegantere Lösung dieses Problems. Das .NET Framework 3.5 lässt allerdings bereits heute auf schönere Ansätze hoffen...


public sealed class Beispiel : SingletonBase<Beispiel>
{
    private Beispiel()
    {
     // Konstruktor
    }
}

Instanzen der Klasse können nun einfach via Beispiel.GetInstance() geholt werden, eine eigene Instanziierung durch Verwendung des Schlüsselwortes new() ist nicht mehr möglich. Ein Haken allerdings bleibt: Die Klasse sollte versiegelt sein, so dass von ihr nicht weiter geerbt werden kann, denn


class GeerbtesBeispiel : Beispiel
{ }

bietet nun auch die statische Methode GetInstance(), die allerdings nach wie vor die Basisklasse Beispiel instanziiert, nicht jedoch GeerbtesBeispiel. 

Damit nun dieser Artikel nicht noch länger wird, als er bisher schon ist, möchte ich dieses Beispiel beim nächsten Mal zu einem systemweiten Singleton ausbauen. Mit Hilfe dieser Methode lässt sich beispielsweise einfach realisieren, dass eine Anwendung nur einmal gestartet werden kann und jeder weitere Programmstart das bereits geöffnete Fenster in den Vordergrund holt. Dazu allerdings erst beim nächsten Mal mehr.

Abschließend bleibt mir eigentlich nur noch zu sagen, dass es sich bei diesem Artikel lediglich um ein Beispiel einer Möglichkeit der Umsetzung handelt. Es gibt noch unzählige weitere Implementierungen des Singleton, sehen Sie es also als eine Art Inspiration für Ihre Projekte, ich hoffe ich konnte Ihnen jedoch ein wenig bei der Lösung Ihres Problemes weiterhelfen.


GPLv3
1 Singleton (Entwurfsmuster): Wikipedia
2 Type.GetConstructor-Methode: Microsoft
3 Generische Typen: Wikipedia

 


Siehe auch:
Sprachelemente von C-Sharp (Wikipedia)


[1]