追記 2019/12/15
コメント欄にてalbireoさんにシングルトンの実装の仕方などについてご指摘いただきましたので、
そちらも一緒にお読みいただければと思います。
はじめに
シングルトンクラスを作り、そのインスタンスを外部のクラスから取得しようとしたら、下記の画像のような例外が発生した。
原因はシングルトンクラスに書いていたlog4netのロガーインスタンスを取得するコード(GetLoggerメソッド)にあった。
色々すっ飛ばしてこの話の結論を先に述べると、「ロガーインスタンス取得コードはフィールドの一番最初に書きましょう」。
もし同じ問題ではまっている方がいればとの思いで記事を書いた(いるかな・・・)。
※シングルトンパターンについて正しくない説明があるかもしれませんので、その点はご了承いただいたうえでお読みください。
環境
・IDEはVisual Studio 2017を使用
問題の発生まで
このサイト(.NET TIPS:シングルトンパターンを実現するには?[C#/VB])を参考にして、シングルトンクラスを作成し、ログ出力するためロガーインスタンス取得コードを書いた。
下記がそのシングルトンクラス。
namespace Hoge
{
class SingletonClass
{
// 自身のクラスのインスタンスを初期化(アプリ実行時にインスタンスはこの1つしか存在しない)
private static SingletonClass _instance = new SingletonClass();
// ロガーインスタンス取得コード
private static readonly log4net.ILog _logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// 自身のインスタンスを外部から使うための手段を用意してあげる
// プロパティを使った方法
public static SingletonClass Instance => _instance;
// メソッドを使った方法
public static SingletonClass GetInstance() => _instance;
// 外部から直接呼べないコンストラクタ
private SingletonClass()
{
_logger.Debug("SingletonClassのインスタンスが生成されました");
}
}
}
そして、シングルトンクラスを利用する側のクラスで、シングルトンインスタンス取得コードを書いた。
namespace Hoge
{
class UseSingleton
{
// シングルトンインスタンスを保持するフィールド
private SingletonClass _singletonInstance;
public void SomeMethod()
{
// シングルトンインスタンスを取得
_singletonInstance = SingletonClass.GetInstance();
}
}
}
問題の原因
問題のコードは下図の矢印の順序で処理が行われている。
まずGetInstanceメソッドを呼び出すと、シングルトンクラス内部ではシングルトンインスタンスを取得しようとする。
その際にシングルトンインスタンスがnewされ、したがってシングルトンクラスのコンストラクタに処理が移る。
ここからが問題。
今回はコンストラクタでロガーを使ったログ出力を行おうとした。
しかしこの時点でロガーインスタンスの取得は実行されていないため_loggerはNullの状態だったのだ。
まだ取得してないはずの_loggerを使おうとしたから、「オブジェクト参照がオブジェクトインスタンスに設定されていません」と怒られたのであった・・・。
解決
シングルトンクラスにおいて、ロガーインスタンス取得コードを自身のインスタンス初期化コードより先に書いてあげ、無事に動いた。
class SingletonClass
{
// ロガーインスタンス取得コード
private static readonly log4net.ILog _logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// 自身のクラスのインスタンスを初期化
private static SingletonClass _instance = new SingletonClass();