it-swarm.com.de

Wie abstrahiere ich eine Singleton-Klasse?

So schreibe ich meine Einzelstunden.

public class MyClass
{
    /// <summary>
    /// Singleton
    /// </summary>
    private static MyClass instance;

    /// <summary>
    /// Singleton access.
    /// </summary>
    public static MyClass Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new MyClass();
            }
            return _instance;
        }
    }

    private MyClass() { .... }
}

Wie erstelle ich ein Singleton-Pattern, das wiederverwendbar ist?

Singleton-Muster stellen die folgenden Herausforderungen dar.

  • Der Konstruktor ist private oder protected.
  • Eine Basisklasse kann eine geerbte Klasse nicht instanziieren. Sie können also eine gemeinsame Zusammenfassung MyAbstractSingletonClass wiederverwenden.
  • Es muss über eine lokale schreibgeschützte Eigenschaft verfügen, um die Instanz abzurufen.

Das Problem

Ich verwende dieses Muster für eine Reihe von Klassen und muss immer denselben Code schreiben. Wie schreibe ich etwas, das wiederverwendet wird, wenn ich einen Singleton brauche?

28
cgTag

Sie können dies erreichen, indem Sie eine Kombination aus selbstreferenzierender generischer Typeinschränkung und einer Typeinschränkung " new () " verwenden.

Die "neue" Einschränkung stellt sicher, dass jede untergeordnete Klasse immer über einen parameterlosen Konstruktor verfügt, sodass _instance = new T(); immer funktioniert.

Die selbstreferenzierende Typeinschränkung stellt sicher, dass die statische Eigenschaft "Instance" immer den richtigen Typ zurückgibt. nicht der "Basis" -Typ. Ihre Singleton-Basisklasse würde in etwa so aussehen:

public abstract class SingletonBase<T> 
    where T : SingletonBase<T>, new()
{
    private static T _instance = new T();
    public static T Instance
    {
        get
        {                
            return _instance;
        }   
    }
}

Ihre Kinderklassen werden so aussehen:

public class MyChildSingleton : SingletonBase<MyChildSingleton>
{
    //Done!
}

Wenn Sie möchten, dass Ihr Singleton allgemein verwendet wird, sollten Sie natürlich auch den Code "Singleton-Instanz erstellen" geringfügig ändern, um das Muster " Double-Check-Lock " oder das Lazy.) Zu verwenden class, um es Thread-sicher zu machen.

Der große Nachteil: Wenn Sie diese Methode verwenden, stellt die Einschränkung "new ()" so ziemlich sicher, dass Ihre Klasse immer über einen öffentlichen, parameterlosen Konstruktor verfügt. Das bedeutet, dass Ihre Endbenutzer immer new MyChildSingleton() anrufen können, wenn sie es wirklich möchten, und Ihre Singleton-Instanz vollständig umgehen. Ihr Singleton würde "nach Konvention" sein, statt streng durchgesetzt. Um das zu umgehen, würde dies ein bisschen mehr Engineering erfordern. In dem obigen Szenario scheint es die Konvention zu sein, dass Sie Ihre statische Instanz "Default" anstelle von "Instance" benennen sollten. Dies vermittelt auf subtile Weise die Tatsache, dass Ihre Klasse eine "vorgeschlagene" Singleton-Instanz anbietet, deren Verwendung jedoch technisch optional ist.

Ich habe einige Versuche unternommen, das Singleton-Muster strikt zu erzwingen, und das Endergebnis war, Reflektion zu verwenden, um manuell einen privaten Konstruktor aufzurufen. Sie können meinen vollständigen Code-Versuch hier sehen.

48
BTownTKD

Die wahre Lösung beginnt mit dem Ansatz von BTownTKD, erweitert sie jedoch mit der Activator.CreateInstance-Methode, die es Ihren Kinderklassen ermöglicht, die privaten Konstruktoren zu behalten.

Elternklasse

public abstract class BaseSingleton<T> where T :
    BaseSingleton<T>
{
    private static readonly ThreadLocal<T> Lazy =
        new ThreadLocal<T>(() =>
            Activator.CreateInstance(typeof(T), true) as T);

    public static T Instance => Lazy.Value;
}

Kinderklasse

public sealed class MyChildSingleton : BaseSingleton<MyChildSingleton>
{
    private MyChildSingleton() { }
}

Vollständiges Implementierungsbeispiel hier

5
Buvy

Zusätzlich zu BTownTKDs Antwort ist es eigentlich ziemlich einfach, einen Konstruktoraufruf zur Laufzeit einzuschränken (nicht sicher beim Kompilieren möglich). Sie müssen lediglich einen geschützten Konstruktor in SingletonBase hinzufügen, der eine Ausnahme auslöst, wenn _instance nicht null ist. Die Ausnahme wird auch dann ausgelöst, wenn der Konstruktor als erstes von außen aufgerufen wird.

Ich habe es geschafft, diese Technik in einer Einzelbasis anzuwenden und faul und fadensicher zu machen, wie hier beschrieben: http://csharpindepth.com/Articles/General/Singleton.aspx

Das Ergebnis (mit Nutzungserklärung):

/// <summary>
/// Generic singleton class, providing the Instance property, and preventing manual construction.
/// Designed as a base for inheritance trees of lazy, thread-safe, singleton classes.
/// Usage:
/// 1. Sub-class must use itself, or its sub-class, as the type parameter S.
/// 2. Sub-class must have a public default constructor (or no constructors).
/// 3. Sub-class might be abstract, which requires it to be generic and demand the generic type
///    have a default constructor. Its sub-classes must answer all these requirements as well.
/// 4. The instance is accessed by the Instance getter. Using a constructor causes an exception.
/// 5. Accessing the Instance property in an inner initialization in a sub-class constructor
///    might cause an exception is some environments.
/// </summary>
/// <typeparam name="S">Lowest sub-class type.</typeparam>
public abstract class Singleton<S> where S : Singleton<S>, new()
{
    private static bool IsInstanceCreated = false;
    private static readonly Lazy<S> LazyInstance = new Lazy<S>(() =>
        {
            S instance = new S();
            IsInstanceCreated = true;
            return instance;
        });

    protected Singleton()
    {
        if (IsInstanceCreated)
        {
            throw new InvalidOperationException("Constructing a " + typeof(S).Name +
                " manually is not allowed, use the Instance property.");
        }
    }

    public static S Instance
    {
        get
        {
            return LazyInstance.Value;
        }
    }
}

Ich muss sagen, dass ich noch keine intensiven Multi-Threading-Tests durchgeführt habe, aber wie einige bereits gesagt haben, können Sie immer den alten Double-Check-Trick verwenden.

5
Eugene Marin

Sie haben Recht - so wie es derzeit steht, können Sie dies nicht erreichen. Sie können es jedoch mit Hilfe von Generics ansprechen. Beachten Sie, dass Sie mit diesem Ansatz eine Singleton-Instanz für jeden eindeutigen abgeleiteten Typ erhalten :

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = new MyDerivedClass();
            Console.WriteLine(x.ToString());
            Console.WriteLine(x.Instance.ToString());

            Console.ReadKey();
        }
    }


    public abstract class MyBaseClass<T> where T : class, new()
    {
        protected T GetInstance()
        {
            if (_instance == null)
            {
                lock (_lockObj)
                {
                    if (_instance == null)
                        _instance = new T();
                }
            }
            return _instance;
        }

        public T Instance
        {
            get { return GetInstance(); }
        }

        private volatile static T _instance;
        private object _lockObj = new object();
    }

    public class MyDerivedClass : MyBaseClass<MyDerivedClass>
    {
        public MyDerivedClass() { }
    }

}
3
slugster

Die einfache Antwort ist, dass Sie das Singleton-Muster nicht in einer Basisklasse implementieren können.

Sie können jedoch andere kreative Entwurfsmuster implementieren, die möglicherweise für das, was Sie erreichen möchten, geeignet sind. Schauen Sie sich zum Beispiel Abstract Factory an.

2
Ilya Kogan

Eine generische, optimierte Implementierung des Thread-sicheren lockless pattern :

public abstract class Singleton<T> where T : class, new() {
    public static readonly T Instance = new T();
}

Der statische Konstruktor wird zugunsten der Leistung ausgeschlossen, da Faulheit nicht erforderlich ist und die Eigenschaft aus Gründen der Kürze durch ein öffentliches Feld ersetzt wird

Implementierung

public sealed class Foo : Singleton<Foo> {
    public void Bar() {
        //...
    }
}

Benutzen

Foo.Instance.Bar();
1
2Toad

Ich habe vor kurzem diese Antwort auf eine verwandte Frage vorgeschlagen:

https://stackoverflow.com/a/20599467

Bei dieser Methode verwaltet die Basisklasse die Erstellung aller Instanzen abgeleiteter Klassen, da alle Konstruktoren der abgeleiteten Klassen ein Objekt benötigen, das nur die Basisklasse bereitstellen kann, und es keine Notwendigkeit für die Beschränkung ohne Parameter besteht.

0
dan

Ich glaube, mit der Lösung von @ Buvy erhalten Sie pro Thread eine Instanz, die möglicherweise die beabsichtigte ist. Sie müssen es ein wenig ändern, um eine einzelne Instanz über Threads hinweg zu erhalten.

public abstract class BaseSingleton<T> 
    where T : BaseSingleton<T>
{
    private static readonly Lazy<T> lazy =
                    new Lazy<T>(() => Activator.CreateInstance(typeof(T), true) as T);

    public static T Instance { get { return lazy.Value; } }

}
0
jalley

Mein vorgeschlagenes Beispiel:

Basisklasse

public abstract class SingletonBase<T> where T : class
{
  private static readonly Lazy<T> sInstance = new Lazy<T>(() => CreateInstanceOfT());

  public static T Instance { get { return sInstance.Value; } }

  private static T CreateInstanceOfT()
  {
    return Activator.CreateInstance(typeof(T), true) as T;
  }
}

Verwendungszweck

public class yourClass : SingletonBase<yourClass>
{
   public yourMethod()
   {
   }
}

verwenden Sie Ihre Singleton-Klasse wie folgt:

yourClass.Instance.yourMethod();

weitere Informationen finden Sie in meiner Antwortquelle in diesem Link

0
Hasan Fathi