連載
» 2017年11月29日 05時00分 公開

.NET TIPS:シングルトンパターンを実現するには?[C#/VB]

あるオブジェクトがアプリ内に1つだけ存在するようにしたいときには、静的クラスを使うかシングルトンパターンを実装する。両者の方法とその違いなどを取り上げる。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載「.NET TIPS」

 「シングルトン」(singleton)とは、一人っ子や独身者など、1つだけの人やものを表す言葉だ。プログラミングでは、稼働中のアプリの中で1つしかないものを指す。

 C#やVisual Basic(以降、VB)でシングルトンを実現するには、静的クラスを使う方法と、シングルトンパターンを実装したクラスを作る方法がある。本稿では両方を紹介する。

POINT シングルトンパターンの実装

シングルトンパターンの実装まとめ シングルトンパターンの実装まとめ


 特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。

 なお、シングルトンパターンは.NET Frameworkバージョン1.0から利用できるが、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2017以降が必要である。また、サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。

using static System.Console;

Imports System.Console

本稿のサンプルコードに必要な宣言(上:C#、下:VB)

静的クラスをシングルトンとして使うには?

 静的クラス(VBではモジュール)のメンバーはアプリに唯一のものとなる(次のコード)。すなわち、静的クラスを普通に使えばシングルトンと同じことなのである。

// 静的クラス
public static class StaticClass //: BindableBase // ←静的クラスは継承を使えない
{
  // 静的プロパティ:このData1はアプリに唯一のものである
  public static string Data1 { get; set; }
}

' モジュール
Public Module StaticClass
  'Inherits BindableBase ' ←モジュールは継承を使えない

  ' モジュールのプロパティ:このData1はアプリに唯一のものである
  Public Property Data1 As String

End Module

静的クラス(モジュール)の例(上:C#、下:VB)
このData1というプロパティはアプリにただ1つだ。
静的クラス(モジュール)を使う欠点としては、継承を使えないことがある(コンパイルエラーになる)。

 「シングルトンが欲しい」というとき、たいていは静的クラス(モジュール)で代用できるのだ。

 ただし、静的クラス(モジュール)では継承が使えないので、そういうときは次に説明するシングルトンパターンの実装を行う。

シングルトンパターンを実装するには?

 クラスに以下の3ステップの実装を行えばよい。

  1. 外部からインスタンス化できないようにする(=コンストラクタをプライベートにする)
  2. インスタンスをプライベートな静的メンバ変数に保持する(インスタンスの生成は静的コンストラクタまたは静的メンバ変数の初期化で行う)
  3. インスタンスを公開する静的なメソッドかプロパティを追加する

 次のコードにシングルトンパターンの実装例を示す。静的クラスとの違いは、ずいぶん複雑になる代わりに、継承が使えることだ。

// シングルトンパターンを実装したクラス
public class SingletonClass : BindableBase
{
  // このクラスが公開するプロパティ
  private string _data1;
  public string Data1
  {
    get => _data1;
    set => base.SetProperty(ref _data1, value);
  }

  // 以下、シングルトンの実装

  // 1. 外からはインスタンス化できないように、コンストラクタをプライベートにする
  private SingletonClass()
  {
    // 動作確認用としてコンソールへ出力
    WriteLine("SingletonClassのインスタンスが生成されました");
  }

  // 2. アプリに唯一のインスタンス(このクラスが初めて使われるときに生成される)
  private static SingletonClass _theInstance = new SingletonClass();

  // 3. インスタンスを外部に公開する静的なメソッドかプロパティを用意する
  public static SingletonClass GetInstance() => _theInstance;
  public static SingletonClass Current => _theInstance;
}

' シングルトンパターンを実装したクラス
Public Class SingletonClass
  Inherits BindableBase

  ' このクラスが公開するプロパティ
  Private _data1 As String
  Public Property Data1 As String
    Get
      Return _data1
    End Get
    Set(value As String)
      MyBase.SetProperty(_data1, value)
    End Set
  End Property

  ' 以下、シングルトンの実装

  ' 1. 外からはインスタンス化できないように、コンストラクタをプライベートにする
  Private Sub New()
    ' 動作確認用としてコンソールへ出力
    WriteLine("SingletonClassのインスタンスが生成されました")
  End Sub

  ' 2. アプリに唯一のインスタンス(このクラスが初めて使われるときに生成される)
  Private Shared _theInstance As New SingletonClass

  ' 3. インスタンスを外部に公開する静的なメソッドかプロパティを用意する
  Public Shared Function GetInstance() As SingletonClass
    Return _theInstance
  End Function
  Public Shared ReadOnly Property Current As SingletonClass
    Get
      Return _theInstance
    End Get
  End Property
End Class

シングルトンパターンの実装例(上:C#、下:VB)
ここでは例としてGetInstance静的メソッドとCurrent静的プロパティの両方を実装してあるが、実際にはどちらか適切な方だけを実装する。インスタンスの生成は静的メンバ変数の初期化時に行っているが、静的メンバは宣言だけにして静的コンストラクタでインスタンスを生成してメンバ変数に代入してもよい。
また、継承が使えることを示すためにBindableBaseクラスを継承している。このBindableBaseクラスは、「特集:C# 7の新機能詳説:第2回 簡潔なコーディングのために」に掲載してあるものである。
なお、.NET Frameworkでは静的な初期化がきちんと実行されるので、C++でのシングルトンパターンのように動的な初期化を行う必要はないし、スレッドセーフでもある。また、.NET Frameworkではシングルトンパターンのインスタンスの破棄はアプリ終了時に自動的に行われる。破棄すべきリソースを保持している場合は、Disposeパターンを実装しておけばよい(「.NET TIPS:確保したリソースを忘れずに解放するには?[C#/VB]」参照)。
2017/12/05追記:静的な初期化では要件を満たせないために動的な初期化を行わねばならない場合には、スレッドセーフを確保するために(静的な初期化とは異なり)ダブルチェックロッキングイディオムを使う必要がある。(MSDN「Implementing Singleton in C#」の「Multithreaded Singleton」を参照)。

 上のSingletonClassクラスの使い方をコンソールアプリの例で示す(次のコード)。外部からはインスタンスが作れないこと、また、繰り返しインスタンスを取得しても同一のインスタンスが返されていることを確認できる。

static void Main(string[] args)
{
  //var singleton = new SingletonClass(); // コンパイルエラー(勝手にインスタンスを作れない)

  WriteLine("プログラム開始");
  // 出力:プログラム開始

  // インスタンスを取得
  var singleton1 = SingletonClass.Current;
  // 出力:SingletonClassのインスタンスが生成されました

  singleton1.Data1 = "singleton1に設定した値";

  // もう一度、インスタンスを取得
  var singleton2 = SingletonClass.Current;
  WriteLine(singleton2.Data1);
  // 出力:singleton1に設定した値

#if DEBUG
  ReadKey();
#endif
}

Sub Main()
  'Dim singleton = New SingletonClass() ' コンパイルエラー(勝手にインスタンスを作れない)

  WriteLine("プログラム開始")
  ' 出力:プログラム開始

  ' インスタンスを取得
  Dim singleton1 = SingletonClass.Current
  ' 出力:SingletonClassのインスタンスが生成されました

  singleton1.Data1 = "singleton1に設定した値"

  ' もう一度、インスタンスを取得
  Dim singleton2 = SingletonClass.Current
  WriteLine(singleton2.Data1)
  ' 出力:singleton1に設定した値

#If DEBUG Then
  ReadKey()
#End If
End Sub

シングルトンパターンを実装したクラスを使う例(上:C#、下:VB)
この例では、動作をはっきりさせるためにインスタンスをローカル変数singleton1/singleton2に代入している。しかし実際には、このくらい単純な処理のときは、SingletonClass.Current.Data1に対して文字列を直接代入したり、あるいは読み出したりすればよい。
また、出力を見ると、インスタンスの生成タイミング(ここでは静的メンバ変数の初期化タイミング)は、アプリ開始時ではなく、そのクラスが実際に使われるときだと分かる(アプリの起動が遅くなることはない)。

まとめ

 シングルトンが必要なとき、多くの場合は静的クラスで足りる。継承を使いたいときなど、静的クラスで実現できないときはシングルトンパターンを実装する。

利用可能バージョン:.NET Framework 1.0以降
カテゴリ:C# 処理対象:デザインパターン
カテゴリ:Visual Basic .NET 処理対象:デザインパターン
関連TIPS:構文:メソッドやプロパティをラムダ式で簡潔に実装するには?[C# 6.0/7.0]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]


■この記事と関連性の高い別の.NET TIPS


「.NET TIPS」のインデックス

.NET TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。