C# でジェネリックスを使用したシングルトンパターンを実装する

 SingletonパターンとGenericsを組み合わせて使うと面白いことができたりします。

static属性メンバの挙動とSingletonパターン

 まずは、static属性メンバの挙動のおさらいから。
 static属性が付与されたメンバはインスタンスではなくクラス自身に属するため、継承により子クラスを複数作った場合、それら子クラスのいずれかから操作を行うとそれ以外の全ての子クラスからアクセスできるstaticメンバの値や状態も問答無用で一緒に変更されてしまいます(子クラスのインスタンスでも、staticメンバ自体は、親クラス自身に属し、単一の存在でインスタンス変数のように複製されないため)。下記にSingletonパターンを使用した例を示します。

class Program {
    static void Main(string[] args) {
        var alpha = Alpha.GetInstance(typeof(Alpha).Name);
        var beta = Beta.GetInstance(typeof(Beta).Name);
        beta.Name = "hoge"; // beta.Nameの値だけ変えたい。Beta→hoge
        Console.WriteLine(" alpha.Name = {0} \r\n beta.Name = {1}", alpha.Name, beta.Name);
    }
}

class Alpha : Base {}
class Beta : Base {}
class Base {
    protected static Base Instance;
    public string Name { get; set; }

    public static Base GetInstance(string name) {
        if (Instance == null) {
            Instance = new Base() { Name = name };
        }
        return Instance;
    }
}

このソースコードを実行すると、次のような結果になります。

 alpha.Name = hoge
 beta.Name = hoge

コーディングしたプログラマーとしては、

 alpha.Name = Alpha
 beta.Name = hoge

このようにbetaのNameの値だけ変えたいという意図があったと思いますが、実際の実行結果ではstatic属性の特性に従いalphaのNameの値まで変わっています(たびたび引っかかるトラップです)。なお、このソースコードでは「beta.Name = "hoge";」が無くても意図通りの結果にはなりません。
 さて、どうやって所期の意図を実現させるかと言うと、ここで本題の「ジェネリックスを使用したシングルトンパターン」が登場します。

SingletonパターンとGenericsを組み合わせて使う

 上記のSingletonパターンを使用したソースコードを以下のように修正します。

// Program クラスは修正部分なし
class Program {
    static void Main(string[] args) {
        var alpha = Alpha.GetInstance(typeof(Alpha).Name);
        var beta = Beta.GetInstance(typeof(Beta).Name);
        beta.Name = "hoge"; // beta.Nameの値だけ変えたい。Beta→hoge
        Console.WriteLine(" alpha.Name = {0} \r\n beta.Name = {1}", alpha.Name, beta.Name);
    }
}

// BaseクラスをBase<T>クラスに置き換えるだけ!
class Alpha : Base<Alpha> {}
class Beta : Base<Beta> {}
class Base<T> where T : class, new() {
    protected static Base<T> Instance;
    public string Name { get; set; }

    public static Base<T> GetInstance(string name) {
        if (Instance == null) {
            Instance = new Base<T>() { Name = name };
        }
        return Instance;
    }
}

これを実行すると、当初のコーディングの意図通り、

 alpha.Name = Alpha
 beta.Name = hoge

という結果になります。
ちなみに「beta.Name = "hoge";」を消した場合、

 alpha.Name = Alpha
 beta.Name = Beta

とこちらの場合も意図通りの値が表示されるようになっています。

まとめ

 Genericsの特性を上手く利用すると、Singletonパターンの実装を継承元クラスに一つに纏める、ということが簡単にできてしまいます。もちろん、今回の小手先のテクニックが通用するのは継承先で同じ(ような)処理をしている場合に限られます。
 このテクニックは他のサイトで見かけたことがないので、もしかしたらこれを使う機会に遭遇した場合は実装が悪いか、これ自体が褒められたものではないか、のどちらかかもしれません。いずれにしてもFlyweightとSingletonを組み合わせても同じようなことができますしね。