前回は二重チェックを用いた方法で、マルチスレッド下で正常に動作するSingletonを説明しました。
今回はもう一つの、staticコンストラクタを用いた方法を紹介します。
それでは、まずはいつものようにコードを掲載します。
C#
/// <summary>
/// ログ出力クラス
/// </summary>
public class SingletonLogger
{
/// <summary>
/// ログ出力パス
/// </summary>
private string _logPath;
/// <summary>
/// プロパティ ログ出力パス
/// </summary>
public string LogPath
{
get
{
return _logPath;
}
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <remarks>new でインスタンス化できないよう、privateでコンストラクタを定義</remarks>
private SingletonLogger()
{
// 重い初期化処理
for (var i = 0; i < 1000000000; i++)
{
}
_logPath = DateTime.Now.ToString("yyyyMMddhhmmssfff") + ".log";
}
/// <summary>
/// 自身の唯一のインスタンス
/// </summary>
private static SingletonLogger _uniqueLogger = new SingletonLogger();
/// <summary>
/// プロパティ インスタンス
/// </summary>
/// <remarks>自身の唯一のインスタンスを返す</remarks>
public static SingletonLogger Instance
{
get
{
return _uniqueLogger;
}
}
/// <summary>
/// ログ出力
/// </summary>
/// <param name="message"></param>
public void WriteLog(string message)
{
// ログ出力処理
// ・・・
}
}
VB
''' <summary>
''' ログ出力クラス
''' </summary>
''' <remarks></remarks>
Public Class SingletonLogger
''' <summary>
''' ログ出力パス
''' </summary>
''' <remarks></remarks>
Private _logPath As String
''' <summary>
''' プロパティ ログ出力パス
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property LogPath() As String
Get
Return _logPath
End Get
End Property
''' <summary>
''' コンストラクタ
''' </summary>
''' <remarks>new でインスタンス化できないよう、privateでコンストラクタを定義</remarks>
Private Sub New()
' 重い初期化処理
For i As Integer = 0 To 1000000000
Next i
_logPath = DateTime.Now.ToString("yyyyMMddhhmmssfff") & ".log"
End Sub
''' <summary>
''' 自身の唯一のインスタンス
''' </summary>
''' <remarks></remarks>
Private Shared _uniqueLogger As New SingletonLogger()
''' <summary>
''' プロパティ インスタンス
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks>自身の唯一のインスタンスを返す</remarks>
Public Shared ReadOnly Property Instance() As SingletonLogger
Get
Return _uniqueLogger
End Get
End Property
''' <summary>
''' ログ出力
''' </summary>
''' <param name="message"></param>
Public Sub WriteLog(ByVal message As String)
' ログ出力処理
' ・・・
End Sub
End Class
では、コードについて説明しましょう。ポイントは
- Singleton自身のインスタンス変数の宣言箇所で初期化も行う。
- Instanceプロパティでは、チェックを行わないで良い。
の2点です。
前回、前々回の方法ではInstanceプロパティにアクセスしたときに、その中で自身のインスタンスを作成しました。これを遅延初期化といいます。
しかし、今回は変数宣言時に初期化しており、このコードが実行されるのは、初めてSingletonのメンバにアクセスしようとした際に実行される、staticコンストラクタ(クラスコンストラクタ)の内部になります。そして、CLRではstaticコンストラクタはスレッドセーフであることが保証されるため、必ず1つしかインスタンスが作られないことになります。
ですので、2.のようにInstanceプロパティ内でのチェックは不要になります。
ところで、この方法は二重チェックの方法に比べて単純ですし、積極的に利用したほうが良いと感じるかもしれません。ただ一点、Singletonのメンバにアクセスしようとした際、それがInstanceプロパティでない場合でも初期化がおこなわれるため、実行時のオーバーヘッドが問題になる場合は使うべきでないときもあります。
が、最近はマシンの性能も上がっていますし、このオーバーヘッドはほぼ無視して良いでしょう。
ということで、Singletonについては、複雑になる二重チェックの方法ではなく、今回のstaticコンストラクタで初期化を行う手法を使っておけば、ほぼ間違いないと考えて良いのではないでしょうか。
さて、Singletonについて3回にわたりお送りしてきました。次回からはCommandパターンを取り上げようと思います。