【C#】シングルトンパターンの実装方法と注意点

シングルトンパターンは「アプリケーション全体でインスタンスをひとつだけにする」デザインパターンです。設定値やロガー、接続管理などの「共有すべきオブジェクト」を管理する際によく利用されます。C# ではさまざまな方法でシングルトンを実装できますが、スレッドセーフ性やテスト容易性に注意が必要です。本記事では代表的な実装方法とその注意点を紹介します。

基本的なシングルトン実装

最もシンプルな方法は、static フィールドに唯一のインスタンスを持たせ、外部からはプロパティ経由でアクセスさせるやり方です。

public sealed class Singleton
{
    private static readonly Singleton _instance = new Singleton();

    // 外部からインスタンス生成を禁止
    private Singleton() { }

    public static Singleton Instance => _instance;

    public void DoSomething()
    {
        Console.WriteLine("処理を実行");
    }
}

// 利用例
Singleton.Instance.DoSomething();

この方法は簡潔で、CLR が static フィールドを初期化する段階でスレッドセーフが保証されます。

遅延初期化(Lazy<T>)を利用する

インスタンス生成コストが高い場合や、使われない可能性がある場合は「遅延初期化」を使うと効率的です。C# には Lazy<T> クラスがあり、スレッドセーフな遅延生成を簡単に書けます。

public sealed class LazySingleton
{
    private static readonly Lazy<LazySingleton> _instance =
        new Lazy<LazySingleton>(() => new LazySingleton());

    private LazySingleton() { }

    public static LazySingleton Instance => _instance.Value;

    public void DoSomething()
    {
        Console.WriteLine("遅延初期化されたシングルトン");
    }
}

Lazy<T> はデフォルトでスレッドセーフに動作し、最初にアクセスされたときだけインスタンスを生成します。

DIコンテナでのシングルトン

ASP.NET Core などでは DI コンテナを使ってシングルトンを管理するのが一般的です。DI コンテナに登録すれば、依存性の注入を通じてシングルトンが利用されます。

// Program.cs 内のサービス登録
builder.Services.AddSingleton<MyService>();

これにより、フレームワークがアプリケーション全体で単一インスタンスを生成・共有してくれます。

注意点

  • スレッドセーフ性: マルチスレッド環境で同時にインスタンス生成が起きないようにする必要があります。static 初期化や Lazy<T> を使えば安全です。
  • テスト容易性: シングルトンはアプリ全体に影響するため、依存するとユニットテストで差し替えが難しくなります。DI で管理する方法を選ぶとテスト性が向上します。
  • 状態を持ちすぎない: シングルトンに可変状態を持たせすぎると「隠れたグローバル変数」となり、予期しない不具合や保守性低下の原因になります。
  • ライフサイクル管理: 特に ASP.NET Core では、シングルトンにスコープド依存を持たせるとライフタイムの不一致が起きるため注意が必要です。

まとめ

C# でのシングルトン実装には複数の方法があります。

  • 最もシンプルな方法は static フィールドを利用する
  • 遅延初期化が必要な場合は Lazy<T> を活用する
  • ASP.NET Core などの実務では DI コンテナにシングルトン登録するのが推奨

シングルトンは便利ですが、使いすぎると設計を硬直化させる原因にもなります。適切な責務に絞り、テスト性を意識した設計を心がけましょう。