C# を使用してアプリケーションを開発する際、オブジェクトの生成と初期化は避けて通れない非常に重要なプロセスです。

その中心的な役割を担うのが「コンストラクタ」です。

コンストラクタを正しく理解し、適切に設計することは、コードの堅牢性や再利用性を高めるだけでなく、バグの混入を防ぐことにも直結します。

本記事では、コンストラクタの基本的な書き方から、継承関係における挙動、さらには最新の C# で導入されたプライマリコンストラクタの活用法まで、プロフェッショナルの現場で求められる知識を網羅的に解説します。

C# コンストラクタの基本概念

コンストラクタとは、クラスのインスタンス(オブジェクト)が生成される際に自動的に実行される特殊なメソッドのことです。

主な目的は、オブジェクトが正しく動作するために必要な初期状態を構築することにあります。

例えば、フィールドへの初期値の代入や、必要なリソースの確保などがここで行われます。

コンストラクタの定義ルール

C# においてコンストラクタを定義する際には、通常のメソッドとは異なるいくつかの厳格なルールが存在します。

  1. 名称の一致:コンストラクタの名前は、必ずそのクラス名と完全に一致させる必要があります。
  2. 戻り値の不在:コンストラクタには戻り値の型を指定しません。void すら記述しないのがルールです。
  3. アクセスの制御:通常は public を指定して外部からのインスタンス化を許可しますが、特定の設計パターン(Singleton など)では private を使用することもあります。

基本的な実装例

以下のコードは、最もシンプルなコンストラクタの実装例です。

Person クラスがインスタンス化される際に、名前と年齢を初期化する構造になっています。

C#
using System;

public class Person
{
    // フィールドの定義
    public string Name { get; set; }
    public int Age { get; set; }

    // 基本的なコンストラクタ
    public Person(string name, int age)
    {
        // 引数で受け取った値をプロパティに代入
        Name = name;
        Age = age;
        Console.WriteLine("Person クラスのインスタンスが生成されました。");
    }

    public void Introduce()
    {
        Console.WriteLine($"名前は {Name}、年齢は {Age} 歳です。");
    }
}

class Program
{
    static void Main()
    {
        // インスタンス化のタイミングでコンストラクタが呼ばれる
        Person person = new Person("田中 太郎", 25);
        person.Introduce();
    }
}
実行結果
Person クラスのインスタンスが生成されました。
名前は 田中 太郎、年齢は 25 歳です。

デフォルトコンストラクタとオーバーロード

C# のクラスには、複数のコンストラクタを定義することができます。

これをコンストラクタのオーバーロードと呼びます。

引数の数や型が異なるコンストラクタを複数用意することで、オブジェクトの生成方法に柔軟性を持たせることが可能です。

デフォルトコンストラクタの挙動

クラスにコンストラクタを一つも記述しなかった場合、コンパイラは自動的に「引数なしの空のコンストラクタ」を作成します。

これをデフォルトコンストラクタと呼びます。

しかし、引数を持つコンストラクタを一つでも明示的に定義すると、コンパイラによる自動生成は行われなくなります。

そのため、引数なしでのインスタンス化も許可したい場合は、明示的に引数なしのコンストラクタを記述しなければなりません。

コンストラクタ・チェイニング(this)

複数のコンストラクタを定義する際、初期化ロジックが重複してしまうことがあります。

このような場合、this キーワードを使用して他のコンストラクタを呼び出すことで、コードの重複を避け、メンテナンス性を向上させることができます。

C#
using System;

public class Product
{
    public string ProductName { get; }
    public decimal Price { get; }

    // メインのコンストラクタ
    public Product(string name, decimal price)
    {
        ProductName = name;
        Price = price;
    }

    // 他のコンストラクタを呼び出す(チェイニング)
    // 価格が指定されない場合はデフォルトで 0 を設定する
    public Product(string name) : this(name, 0)
    {
        Console.WriteLine("価格未指定のため、0円として初期化しました。");
    }
}

class Program
{
    static void Main()
    {
        Product p1 = new Product("ノートPC", 150000);
        Product p2 = new Product("サンプル品");

        Console.WriteLine($"{p1.ProductName}: {p1.Price}円");
        Console.WriteLine($"{p2.ProductName}: {p2.Price}円");
    }
}
実行結果
価格未指定のため、0円として初期化しました。
ノートPC: 150000円
サンプル品: 0円

このように、共通の初期化処理を一箇所に集約することは、DRY(Don’t Repeat Yourself)原則に基づいた良質なコードを書くための基本です。

継承関係におけるコンストラクタ

C# の継承において、コンストラクタは少し特殊な挙動を示します。

派生クラス(子クラス)のインスタンスを生成する際、必ず基底クラス(親クラス)のコンストラクタが先に実行されるというルールがあります。

base キーワードによる基底クラスの呼び出し

派生クラスのコンストラクタから、基底クラスの特定のコンストラクタを呼び出すには base キーワードを使用します。

もし基底クラスに引数なしのコンストラクタが存在しない場合、派生クラス側で明示的に base(...) を呼び出す必要があります。

C#
using System;

// 基底クラス
public class Animal
{
    public string Species { get; }

    public Animal(string species)
    {
        Species = species;
        Console.WriteLine($"Animal コンストラクタ: 種別は {Species}");
    }
}

// 派生クラス
public class Dog : Animal
{
    public string Name { get; }

    // base を使用して親クラスのコンストラクタに値を渡す
    public Dog(string name) : base("犬")
    {
        Name = name;
        Console.WriteLine($"Dog コンストラクタ: 名前は {Name}");
    }
}

class Program
{
    static void Main()
    {
        Dog myDog = new Dog("ポチ");
    }
}
実行結果
Animal コンストラクタ: 種別は 犬
Dog コンストラクタ: 名前は ポチ

実行順序に注目してください。

まず Animal のコンストラクタが完了し、その後に Dog のコンストラクタ内の処理が実行されます。

この順序は、オブジェクトの土台となる親の部分を先に完成させる必要があるためです。

静的コンストラクタ(static constructor)

通常のコンストラクタがインスタンスごとに実行されるのに対し、静的コンストラクタはクラスそのものに対して一度だけ実行されます。

主に static フィールドの初期化や、クラスを利用する前に行うべき一度限りの設定処理に使用されます。

静的コンストラクタの特徴

  • アクセス修飾子(publicprivate)を記述することはできません。
  • 引数を持つことはできません。
  • ユーザーが直接呼び出すことはできず、そのクラスの最初のインスタンスが作成されるか、静的メンバーにアクセスされる直前に .NET ランタイムによって自動的に呼び出されます。
C#
using System;

public class Configuration
{
    public static readonly DateTime AppStartTime;

    // 静的コンストラクタ
    static Configuration()
    {
        AppStartTime = DateTime.Now;
        Console.WriteLine("静的コンストラクタが実行されました。");
    }

    public static void ShowTime()
    {
        Console.WriteLine($"アプリ開始時刻: {AppStartTime}");
    }
}

class Program
{
    static void Main()
    {
        Console.WriteLine("Main 開始");
        Configuration.ShowTime(); // ここで静的コンストラクタが動く
        Configuration.ShowTime();
    }
}
実行結果
Main 開始
静的コンストラクタが実行されました。
アプリ開始時刻: 202X/XX/XX XX:XX:XX
アプリ開始時刻: 202X/XX/XX XX:XX:XX

一度実行されると、その後何度クラスのメンバーを呼び出しても再実行されることはありません。

最新機能:プライマリコンストラクタ(C# 12~)

C# 12 から、クラスや構造体の定義をより簡潔に記述できるプライマリコンストラクタが導入されました。

これにより、ボイラープレート(定型的なコード)を大幅に削減することが可能になりました。

従来の書き方との比較

これまで、コンストラクタの引数をプライベートフィールドに代入するだけのコードを何度も書いてきたはずです。

C#
// 従来の書き方
public class User
{
    private readonly string _name;
    public User(string name)
    {
        _name = name;
    }
}

プライマリコンストラクタの構文

プライマリコンストラクタを使用すると、クラス名のすぐ後ろに引数を記述できます。

この引数は、クラス内のどこからでも参照可能なスコープを持ちます。

C#
using System;

// クラス定義に直接引数を書く
public class Employee(int id, string name)
{
    public void DisplayInfo()
    {
        // id と name はクラス全体で利用可能
        Console.WriteLine($"ID: {id}, Name: {name}");
    }
}

class Program
{
    static void Main()
    {
        var emp = new Employee(101, "佐藤 健二");
        emp.DisplayInfo();
    }
}

注意点と活用シーン

プライマリコンストラクタの引数は、あくまで「引数」であり、自動的に公開プロパティになるわけではありません(record の場合とは挙動が異なります)。

そのため、外部からアクセスさせたい場合は、明示的にプロパティに代入する必要があります。

C#
public class Manager(string department)
{
    // 引数を使用してプロパティを初期化
    public string Department { get; } = department;
}

この機能は、特に依存性の注入(Dependency Injection / DI)を行う際に威力を発揮します。

サービスの注入コードが一行で済むようになるため、モダンな ASP.NET Core 開発などでは標準的な記述スタイルになりつつあります。

特殊なコンストラクタの活用パターン

コンストラクタは単に値を代入するだけでなく、特定の設計パターンを実現するためにも利用されます。

プライベートコンストラクタと Singleton パターン

コンストラクタを private に設定すると、外部から new 演算子でインスタンス化することができなくなります。

これを利用して、システム内でインスタンスが一つであることを保証するSingleton パターンを実装できます。

C#
public class DatabaseConnection
{
    private static DatabaseConnection _instance;

    // 外部からの生成を禁止
    private DatabaseConnection() { }

    public static DatabaseConnection Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new DatabaseConnection();
            }
            return _instance;
        }
    }
}

必須プロパティとコンストラクタ

C# 11 で導入された required 修飾子を使用すると、コンストラクタで値を設定するか、オブジェクト初期化子で値を設定することを強制できます。

これにより、コンストラクタのオーバーロードを増やしすぎることなく、不完全なオブジェクトの生成を防止できます。

C#
public class Customer
{
    // インスタンス化の際に設定が必須となる
    public required string Email { get; init; }
    public string FullName { get; set; }
}

// 呼び出し側
// var c = new Customer(); // コンパイルエラー:Email が指定されていない
var c = new Customer { Email = "test@example.com" }; // OK

コンストラクタ設計におけるベストプラクティス

より良い C# プログラムを書くために、コンストラクタ設計における指針をいくつか紹介します。

1. 重い処理を避ける

コンストラクタの中で、データベースへの接続や重いファイルの読み込みといった処理を行うのは避けるべきです。

コンストラクタはあくまで「状態の初期化」に専念させるべきであり、失敗する可能性の高い外部アクセスなどは、別途 InitializeAsync() などのメソッドを用意するか、ファクトリパターンを検討してください。

2. 依存関係の注入(DI)を活用する

具象クラスをコンストラクタ内で直接生成(new)するのではなく、インターフェースを通じて外部から渡すように設計します。

これにより、ユニットテストが容易になり、コードの結合度を下げることができます。

3. readonly メンバーの初期化

インスタンス生成後に値を変更させたくないフィールドは、readonly 修飾子を付けます。

これらのフィールドは、宣言時またはコンストラクタ内でのみ値を割り当てることが可能です。

C#
public class SecureClient
{
    private readonly string _apiKey;

    public SecureClient(string apiKey)
    {
        // コンストラクタでのみ代入可能
        _apiKey = apiKey;
    }
}

まとめ

C# のコンストラクタは、言語の進化とともにその記述方法も洗練されてきました。

基本的なインスタンスコンストラクタから、クラス全体の初期化を担う静的コンストラクタ、継承関係を制御する base 呼び出し、そして最新のプライマリコンストラクタまで、各機能には明確な役割があります。

  • 基本:クラス名と同じ名称で、初期化を担当する。
  • オーバーロードthis を活用して重複を避けつつ、複数の生成手段を提供する。
  • 継承base を通じて親クラスの初期化を確実に実行する。
  • 最新機能:プライマリコンストラクタでボイラープレートを削減し、コードの視認性を高める。

これらの概念を適切に使い分けることで、バグが少なく、読みやすいオブジェクト指向プログラムを構築できるようになります。

特に C# 12 以降の環境では、プライマリコンストラクタを積極的に活用して、シンプルで現代的なコーディングスタイルを取り入れてみてください。