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
とこちらの場合も意図通りの値が表示されるようになっています。