web-dev-qa-db-ja.com

シングルトンクラスを抽象化する方法

これが、シングルトンクラスの記述方法です。

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() { .... }
}

再利用可能なシングルトンパターンを作成する方法

シングルトンパターンには、次の課題があります。

  • コンストラクタはprivateまたはprotectedです。
  • 基本クラスは、継承されたクラスをインスタンス化できません。したがって、共通の抽象MyAbstractSingletonClassを再利用できます。
  • インスタンスを取得するには、ローカルの読み取り専用プロパティが必要です。

問題

私は多くのクラスでこのパターンを使用しており、常に同じコードを書かなければなりません。シングルトンが必要なときにいつでも再利用できるものを書くにはどうすればよいですか?

30
Reactgular

自己参照ジェネリック型制約 と " new() "型制約の組み合わせを使用して、これを実現できます。

「新しい」制約により、すべての子クラスが常にパラメーターなしのコンストラクターを持つことが保証されるため、_instance = new T();は常に機能します。

自己参照型の制約により、 "Instance"静的プロパティは常に正しい型を返します。 「ベース」タイプではありません。シングルトン基本クラスは次のようになります。

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

子クラスは次のようになります。

_public class MyChildSingleton : SingletonBase<MyChildSingleton>
{
    //Done!
}
_

もちろん、シングルトンを汎用にしたい場合は、「シングルトンインスタンスの作成」コードを少し変更して、「 double-check lock 」パターンを使用するか、 遅延 クラス。スレッドセーフにします。

注意点:このメソッドを使用する場合、「new()」制約により、クラスに常にパラメーターのないパブリックコンストラクターが確実に確保されます。つまり、エンドユーザーは、シングルトンインスタンスを完全にバイパスして、本当に必要な場合は常にnew MyChildSingleton()を呼び出すことができます。シングルトンは、厳密に強制されるのではなく、「慣例により」なります。これを回避するには、もう少しエンジニアリングが必要になります。上記のシナリオでは、慣例により、静的インスタンスには「Default」ではなく「Instance」という名前を付ける必要があります。これは、クラスが「推奨」シングルトンインスタンスを提供するという事実を微妙に伝えますが、それを使用することは技術的にオプションです。

シングルトンパターンを厳密に適用するためにいくつかの試みを行った結果、リフレクションを使用してプライベートコンストラクターを手動で呼び出しました。あなたは私の完全なコードの試み ここ を見ることができます。

50
BTownTKD

真の解決策は、BTownTKDのアプローチから始まりますが、子クラスがプライベートコンストラクターを保持できるようにするActivator.CreateInstanceメソッドでそれを補強します。

親クラス

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;
}

子クラス

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

ここに完全な実装例

6
Buvy

BTownTKDの答えに加えて、実行時にコンストラクター呼び出しを制限することは実際には非常に簡単です(コンパイルでは可能かどうかはわかりません)。行うのは、SingletonBaseに保護されたコンストラクタを追加することだけです。これは、_instanceがnullでない場合に例外をスローします。コンストラクタが最初に外部から呼び出された場合でも、例外がスローされます。

私はこのテクニックをシングルトンベースに適用し、ここで説明されているように遅延してスレッドセーフにすることもできました: http://csharpindepth.com/Articles/General/Singleton.aspx

結果(使用法の説明付き):

/// <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;
        }
    }
}

私は集中的なマルチスレッドテストを行っていないと言っておく必要がありますが、すでに述べたように、古いダブルチェックトリックをいつでも使用できます。

5
Eugene Marin

あなたは正しいです-現在のところ、これを達成することはできません。ただし、ジェネリックを使用してアプローチすることもできます。このアプローチでは一意の派生型ごとに1つのシングルトンインスタンスが得られることに注意してください。

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() { }
    }

}
4
slugster

簡単な答えは、基本クラスにシングルトンパターンを実装できないことです。

ただし、達成しようとしていることに適した他の創造的なデザインパターンを実装できます。たとえば、 Abstract Factory を見てください。

2
Ilya Kogan

スレッドセーフな汎用の合理化された実装 ロックレスパターン

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

遅延は必要ないため、静的コンストラクタはパフォーマンスを優先して除外され、プロパティは簡潔にするためにパブリックフィールドに置き換えられます

実装

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

使用

Foo.Instance.Bar();
1
2Toad

@Buvyのソリューションでは、彼が意図したものである可能性があるスレッドごとのインスタンスを取得すると思います。スレッド間で単一のインスタンスを取得するには、少し変更する必要があります。

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; } }

}
1
jalley

私は最近、関連する質問に対するこの回答を提案しました:

https://stackoverflow.com/a/20599467

このメソッドを使用すると、すべての派生クラスコンストラクターは基本クラスのみが提供できるオブジェクトを必要とし、パラメーターなしのコンストラクター制限の必要がないため、基本クラスは派生クラスのすべてのインスタンスの作成を管理します。

0
dan

私の提案する例:

基本クラス

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;
  }
}

用途

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

次のようにシングルトンクラスを使用します。

yourClass.Instance.yourMethod();

詳細については、私の回答ソースを このリンク で参照してください

0
Hasan Fathi