Singleton-Diskussion

    • C#

    Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von nafets3646.

      Singleton-Diskussion

      Abend,

      hier mal ein Beitrag zum Thema Singleton.
      Als Entwickler möchte man ab und an sicherstellen, dass von einer Klasse maximal eine Instanz erzeugt wird.
      Hierzu verwendet man ein Singleton (-> de.wikipedia.org/wiki/Singleton_(Entwurfsmuster))
      Man beachte ebenfalls die folgenden Beiträge zum Thema.

      Implementierung
      Wie geht man also vor, um nur eine Instanz zu erlauben? Das ist im Prinzip ganz einfach: Man verwendet eine
      statische (VB: Shared) Funktion, die eine Instanz zurückgibt. Theoretisch kann man hier auch mit einer Property arbeiten.
      Man erhält also immer die selbe Instanz.

      C#-Quellcode

      1. class SingleInstance
      2. {
      3. private static SingleInstance Instance = new SingleInstance();
      4. private SingleInstance() { }
      5. public static SingleInstance GetInstance()
      6. {
      7. if (Instance == null)
      8. {
      9. Instance = new SingleInstance();
      10. return Instance;
      11. }
      12. else { return Instance; }
      13. }
      14. }


      Grüße
      "Life isn't about winning the race. Life is about finishing the race and how many people we can help finish the race." ~Marc Mero

      Nun bin ich also auch soweit: Keine VB-Fragen per PM! Es gibt hier ein Forum, verdammt!
      kleine Vereinfachung und Korrektur:

      C#-Quellcode

      1. class SingleInstance {
      2. public static readonly SingleInstance Instance = new SingleInstance();
      3. private SingleInstance() { }
      4. }
      Deine if-Abfrage ist üflüssig, denn Instance kann überhaupt nicht null werden.
      Hingegen der private Konstruktor ist erforderlich, um zu gewährleisten, dass tatsächlich niemand von aussen diese Klasse instanzieren kann.
      Man sollte in diesem Fall auch den Konstruktor nach außen hin schützen, sonst könnten theoretisch trotzdem beliebig viele Instanzen erstellt werden.

      Wenn man die Klasse sealed setzt (was wohl das logischste ist):

      C#-Quellcode

      1. sealed class SingleInstance
      2. {
      3. public static readonly SingleInstance Instance = new SingleInstance();
      4. private SingleInstance()
      5. { }
      6. }

      Oder andernfalls:

      C#-Quellcode

      1. class SingleInstance
      2. {
      3. public static readonly SingleInstance Instance = new SingleInstance();
      4. protected SingleInstance()
      5. { }
      6. }​
      Im Startpost von @Nikx wird der Singleton mit dem Lazy-Pattern kombiniert.

      Wenn man eine Property verwendet, kann man den statischen Konstruktor benutzen, um die Instanz zu erstellen:


      C#-Quellcode

      1. sealed class SingleInstance
      2. {
      3. public static SingleInstance Instance { get; private set; }
      4. static SingleInstance()
      5. {
      6. Instance = new SingleInstance();
      7. }
      8. private SingleInstance()
      9. { }
      10. }
      Das sollte man später mit Roslyn auch anders machen können, vorerst muss man bei Propertys noch explizit den statischen Konstruktor (oder ähnlich) setzen.

      Mit Lazy-Init kombiniert (wie im Startpost) sieht man häufig sowas:

      C#-Quellcode

      1. sealed class SingleInstance
      2. {
      3. private static SingleInstance _instance;
      4. public static SingleInstance Instance
      5. {
      6. get
      7. {
      8. return _instance ?? (_instance = new SingleInstance()); // Wie: _instance != null ? _instance : (_instance = new SingleInstance());
      9. }
      10. }
      11. private SingleInstance()
      12. { }
      13. }
      (IMO best practice, wenn man es Lazy braucht/will)
      Von meinem iPhone gesendet
      Hatte das mal irgendwo so gelesen aber nicht weiter beachtet. Der Grundgedanke bleibt ja der selbe.
      Aber danke für die Infos :)
      "Life isn't about winning the race. Life is about finishing the race and how many people we can help finish the race." ~Marc Mero

      Nun bin ich also auch soweit: Keine VB-Fragen per PM! Es gibt hier ein Forum, verdammt!
      Vielleicht sollte man das Ganze noch Thread-sicher machen:

      C#-Quellcode

      1. sealed class SingleInstance
      2. {
      3. private static SingleInstance _instance;
      4. private static System.Threading.Mutex _mutex = new System.Threading.Mutex();
      5. public static SingleInstance Instance
      6. {
      7. get
      8. {
      9. _mutex.WaitOne();
      10. try{
      11. _instance = _instance ?? (_instance = new SingleInstance());
      12. }
      13. finally{
      14. _mutex.ReleaseMutex();
      15. }
      16. return _instance;
      17. }
      18. }
      19. private SingleInstance()
      20. { }
      21. }

      (Ich hoffe, ich habe das Lazy-Init-Zeugs richtig interpretiert)

      Higlav schrieb:

      Ich hoffe, ich habe das Lazy-Init-Zeugs richtig interpretiert
      Nicht ganz. Bei dir wird imemr eine neue Instanz erstellt, wenn der Getter aufgerufen wird.

      Mein Beispiel oben benutyt eine eigenschaft von C-Artigen Sprachen, dass folgendes geht:

      C#-Quellcode

      1. int a = 10;
      2. int b = a = 12;
      3. // "a = 12" gibt das rechts neben dem = zurück, also 12
      4. // somit ist a == 12 == b
      5. _instance ?? (_instance = new Instance());
      6. // wenn _instance != null, nimm das,
      7. // wenn nicht, nimm den Ausdruck "_instance = new Instance()"
      8. // dieser weißt _instance eine neue Instanz zu und gibt diese dann auch gleich zurück


      In VB gibt es zwar auch einen ??-Operator (geht über If(a, b)), aber sowas wie a = b = c; geht nicht.

      Threadlocking ist nicht so mein Ding, aber warum benutzt du hier Mutex.WaitOne statt einem lock?
      Von meinem iPhone gesendet
      Thread-safe ist immer so ne Sache...
      Zum einen kenne ich folgendes (sehr einfach und auch die Ausgangslage dieses Threads):

      C#-Quellcode

      1. public static SingleInstance Instance = new SingleInstance();


      Zum anderen wurde ja schon das Lazy Pattern erwähnt. Dort wurde auch schon eine Lösung gepostet. Diese ist jedoch etwas ... naja.
      a) Kompliziert
      b) Fehlerhaft

      Fehlerhaft... weshalb? Der erste Fehler der mir sofort auffällt (welcher auch zugleich absolut fatal ist), ist, dass die Mutex nur dann freigegeben wird, wenn im Konstruktor von SingleInstance eine Exception geschmissen wird. Da der Konstruktor jedoch leer ist, ist das hinfällig. Hier würde finally Abhilfe schaffen. Der zweite Fehler der mir auffällt ist, dass die Mutex nirgends disposed wird. Üblicherweise in einer Dispose methode. Das ist jedoch bei Singletons teils recht schwierig, da sichergestellt sein muss, dass keine weiteren Zugriffe auf die freigegebene Instanz erfolgen (deshalb lasse ich das jetzt mal im Code weg). Als letzter Punkt (ist eigentlich unwichtig) will ich nur noch erwähnen, dass man sich das sealed sparen kann. Zum einen sollte man das so oder so nur einsetzen wenn es Sinn macht ein Objekt zu versiegeln. Zum anderen ist es bereits durch den privaten Konstruktor versiegelt (ein "friend" Konzept gibt es in C# ja nicht). Würde somit den Code auf Beispielsweise sowas abändern (ohne jetzt mal die Verwendung einer Mutex in Frage zu stellen):

      C#-Quellcode

      1. sealed class SingleInstance
      2. {
      3. private static SingleInstance _instance;
      4. //todo: _mutex.Dispose()
      5. private static System.Threading.Mutex _mutex = new System.Threading.Mutex();
      6. public static SingleInstance Instance
      7. {
      8. get
      9. {
      10. _mutex.WaitOne();
      11. try
      12. {
      13. return _instance ?? (new SingleInstance());
      14. }
      15. finally
      16. {
      17. _mutex.ReleaseMutex();
      18. }
      19. }
      20. }
      21. private SingleInstance()
      22. {
      23. }
      24. }


      Diese Implementation ist jedoch wie gesagt recht kompliziert. Würde es doch so viel einfacher gehen:

      C#-Quellcode

      1. sealed class SingleInstance
      2. {
      3. private static Lazy<SingleInstance> _instance =
      4. new Lazy<SingleInstance>(LazyThreadSafetyMode.ExecutionAndPublication); //eventuell auch nur PublicationOnly
      5. public static Singleton Instance { get { return _instance.Value; } }
      6. private SingleInstance()
      7. {
      8. }
      9. }


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.

      nikeee13 schrieb:

      Nicht ganz. Bei dir wird imemr eine neue Instanz erstellt, wenn der Getter aufgerufen wird.

      Ou, jetzt verstehe ich, glaube ich. Ich hab's geändert. :thumbup:

      nikeee13 schrieb:

      Threadlocking ist nicht so mein Ding, aber warum benutzt du hier Mutex.WaitOne statt einem lock?

      Ich habe einfach von Klaus Löffelmann's VB-2010-Büchlein abgetippelt(und halt nach C# übersetzt). ^^ Kann gut sein, dass das Lock-Statement angebrachter wäre...

      @ErfinderDesRades Jup. Hier hört das Thema Threadsicherheit auf. :)
      Sorry, doch noch ein Post zum Thema Threadsicherheit ;) .
      Ich finde das Beispiel von der MSDN eigentlich auch ganz gut:

      C#-Quellcode

      1. public sealed class Singleton
      2. {
      3. private static volatile Singleton instance;
      4. private static object syncRoot = new Object();
      5. private Singleton() {}
      6. public static Singleton Instance
      7. {
      8. get
      9. {
      10. if (instance == null)
      11. {
      12. lock (syncRoot)
      13. {
      14. if (instance == null)
      15. instance = new Singleton();
      16. }
      17. }
      18. return instance;
      19. }
      20. }
      21. }
      hihi - ich hab den Thread extra in den SCA kopiert, denn hier sind Diskussionen über Code-Varianten ja erwünscht. :)

      ansonsten sag mal die url.

      und nach meim Verständnis des volatile - Schlüsselwortes müsste dieses das locken auch üflüssig machen.

      vor allem: bei einem Singleton muss doch nicht jeder Property-Abruf gelockt werden, sondern nur die Instanzierung, wenn das lazy-loading aktiv wird.


      edit: ups - haste recht: wird ja nur beim Instanzieren gelockt.

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

      @ErfinderDesRades
      Es wird ja nicht jeder Aufruf gelockt?!
      Erst wird geprüft, ob die Variable schon instanziert ist, wenn ja, wird gelockt und dann die Instanz erstellt.
      Und hier noch der MSDN-Artikel zum Thema, da ist das auch gut beschrieben finde ich: msdn.microsoft.com/en-us/library/ff650316.aspx
      jo, Super-Artikel, und macht eiglich diesen Thread (ausser das Lazy<T>) und sogar den Singleton-Tipp selbst obsolet.
      Komischerweise gibts da den Hinweis, es sei Retired Content, und man solle aktuellere Information aufsuchen, nur wo, sagen sie nicht - MSDN halt :thumbdown:

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Der Thread war eigentlich von Anfang an nur dazu gedacht, die Grundidee dahinter mal zu zeigen.
      Ich hätte niemals gedacht, dass es da noch so viel Zeugs zu beachten gibt, sonst hätte ich es mit eingefasst.
      Ich hab oben jetzt einfach mal auf die folgenden Beiträge verwiesen, denn die sind wesentlich besser als
      mein eigener Code ^^

      Grüße
      "Life isn't about winning the race. Life is about finishing the race and how many people we can help finish the race." ~Marc Mero

      Nun bin ich also auch soweit: Keine VB-Fragen per PM! Es gibt hier ein Forum, verdammt!