読者です 読者をやめる 読者になる 読者になる

C#でGenericなSingleton

c#

C#でGenericなSingletonを実装したい。Genericでない基本的なSingletonの実装法についてはMSDNに書いてある。

MSDNに載っていたSingleton実装

Implementing Singleton in C# 

最も基本的なのは、以下の様な感じ。

public class Singleton
{
   private static Singleton instance;

   private Singleton() {}

   public static Singleton Instance
   {
      get
      {
         if (instance == null)
         {
            instance = new Singleton();
         }
         return instance;
      }
   }
}

この実装は、スレッドセーフではない。
複数のスレッドから同時にInstanceにアクセスされると、タイミングによってはinstanceが複数できてしまう場合がある。
これは避けたいので、以下の様な実装が勧められている。

public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();
   
   private Singleton(){}

   public static Singleton Instance
   {
      get 
      {
         return instance; 
      }
   }
}

もう一個、instanceの生成を遅らせたい場合や、規定以外のコンストラクタを使いたい場合のシングルトンの作り方も載っていて、ここには引用しないが、それはinstanceの生成をlockブロックで囲って排他制御するような実装になっていた。

GenericなSingleton

基本的には、MSDNで二番目に載っていたものをベースに考える。

C++のtemplateでの感覚で

public class Singleton<T> : T
{
    private static readonly T _instance = new T();
    
    ...
}

のような形を考えていたのだけど、どうもこれはうまくいかない。
Genericはtemplateと違ってランタイム時に解決されるものらしく、そのため、これを許すと色々と問題が出てくる。
なので、ちょっと色々と制限を掛けないと駄目らしい。
Tはclassで、new T()ができますよー、という誓約を掛ける必要がある。
なので、以下の様な感じにする。

public class Singleton<T> where T : class, new()
{
    private static readonly T _instance = new T();
    
    ...
}

最終的には、以下の様な感じに

public class Singleton<T> where T : class, new()
{
    // 万一、外からコンストラクタを呼ばれたときに、ここで引っ掛ける
    protected Singleton()
    {
         Debug.Assert( null == _instance );
     }
    private static readonly T _instance = new T();
    
    public static T Instance {
         get {
             return _instance;
         }
    }
}

Singletonにしたいクラスがあるときは、

public class MySingleton : Singleton< MySingleton >
{
    public MySingleton()
    {
    }
    
    ...
}

として利用する。

だいたい目的は達成されたのだけど、気になる点が1点。

これを利用するためには継承先のコンストラクタをpublicにしておかないと行けない。
なのでシングルトンなのに外からコンストラクタを呼ばれてしまってオブジェクトが複数作られてしまう可能性をコンパイル時に防げない。

これが、C++だったら、

public class MySingleton : Singleton< MySingleton >
{
    private MySingleton()
    {
    }
    friend class Singleton<MySingleton>;
}

として防ぐことができるのだけど、C#にはfriend classというものはないので、それは不可能。

まああまりその手のバグに引っかかったこともないし、ランタイム時にはAssertで引っかけることができるので、この当たりで妥協しようかと思う。