C#でクラスを設計するとき、コンストラクタでどのように初期化するかは、可読性や保守性に大きく影響します。
本記事では、C#でよく使われるコンストラクタによる初期化パターンを、実装例とともに整理して解説します。
基本から少し発展的な書き方まで、順番に理解できる構成にしています。
【C#】コンストラクタでの初期化パターンと実装例まとめ
コンストラクタと初期化の基本
コンストラクタとは何か

コンストラクタは、クラスからインスタンスを生成するときに自動的に呼び出される特別なメソッドです。
名前はクラス名と同じにし、戻り値を指定しません。
主な役割は、次のようにまとめられます。
- フィールドやプロパティの初期化
- 必須情報の受け取りと検証
- リソースの確保(ファイル、接続など)
シンプルなコンストラクタの例
using System;
public class User
{
// フィールド
private string _name;
private int _age;
// コンストラクタ
public User(string name, int age)
{
// 引数で受け取った値をフィールドに代入して初期化
_name = name;
_age = age;
}
public void Print()
{
Console.WriteLine($"Name: {_name}, Age: {_age}");
}
public static void Main()
{
// コンストラクタを使ってインスタンスを生成
User user = new User("Taro", 30);
user.Print();
}
}
Name: Taro, Age: 30
ここではコンストラクタに引数を渡して、その値をフィールドに設定しています。
これが最も基本的な初期化パターンです。
フィールドとプロパティの初期値設定
フィールドの初期値とコンストラクタ

C#では、クラスのフィールドは宣言時に初期化することも、コンストラクタ内で初期化することもできます。
public class Settings
{
// 宣言と同時に初期値を設定
private string _language = "ja";
private int _timeoutSeconds = 30;
// 引数なしコンストラクタ
public Settings()
{
// ここでは追加の初期化処理を行ってもよい
// 例: ログ出力、環境による調整など
}
}
宣言時初期化はどのコンストラクタから生成しても同じ既定値を使いたい場合に適しています。
一方で、コンストラクタの中で初期化する場合は、引数や条件に応じて柔軟な初期値設定が可能になります。
自動実装プロパティの初期値
最近のC#では、自動実装プロパティにも初期値を直接書けます。
フィールドを露出せず、プロパティでカプセル化したい場合に便利です。
public class Product
{
// 自動実装プロパティに初期値を設定
public string Name { get; set; } = "Unknown";
public decimal Price { get; set; } = 0m;
// 引数なしコンストラクタ
public Product()
{
// Name と Price はすでに上記の値で初期化済み
}
}
宣言時の初期化 + 引数なしコンストラクタは、シンプルなクラスでよく使われるパターンです。
コンストラクタのオーバーロードと共通化
複数の初期化パターンを提供する

1つのクラスに対して、状況に応じた複数のコンストラクタを用意することをコンストラクタのオーバーロードと呼びます。
public class User
{
private string _name;
private int _age;
// メインとなるコンストラクタ
public User(string name, int age)
{
_name = name;
_age = age;
}
// 年齢を省略できるコンストラクタ
public User(string name) : this(name, 0) // コンストラクタチェーン
{
}
// 何も指定しないコンストラクタ
public User() : this("NoName", 0) // デフォルト値に集約
{
}
}
このように1つのコンストラクタにロジックを集約し、他のコンストラクタからthis(...)で呼び出すことをコンストラクタチェーンと呼びます。
これにより初期化処理が一か所に集まり、保守がしやすくなります。
コンストラクタチェーンのメリット
コンストラクタチェーンを使うと、次のようなメリットがあります。
- 初期化ロジックが重複しない
- デフォルト値の変更を1か所に集約できる
- インスタンスの状態が一貫しやすい
複数のコンストラクタで同じような代入処理をコピペするのは避けるのがよい設計です。
デフォルト値とオプション引数
オプション引数で簡潔に書く

C#では、コンストラクタにもオプション引数を使うことができます。
public class LogConfig
{
public string LogDirectory { get; }
public string FileName { get; }
// オプション引数を使ったコンストラクタ
public LogConfig(string logDirectory = "/var/log", string fileName = "app.log")
{
LogDirectory = logDirectory;
FileName = fileName;
}
}
この場合、次のようにさまざまな呼び出し方ができます。
var config1 = new LogConfig(); // 両方デフォルト
var config2 = new LogConfig("/tmp/logs"); // fileName は既定値
var config3 = new LogConfig(fileName: "test.log"); // 名前付き引数で一部だけ指定
コンストラクタの数を増やさずに複数パターンの初期化を提供できる一方で、引数が増えると呼び出し側の可読性が落ちることもあるため、使いどころを意識することが大切です。
静的コンストラクタと静的初期化
静的コンストラクタの役割

C#には、インスタンスではなく型そのものを初期化するためのコンストラクタとしてstaticコンストラクタがあります。
public class AppSettings
{
public static string ApplicationName { get; }
public static DateTime StartTime { get; }
// 静的コンストラクタ
static AppSettings()
{
// クラスが初めて参照されたタイミングで1回だけ実行される
ApplicationName = "MyApp";
StartTime = DateTime.Now;
}
}
静的コンストラクタの特徴は次の通りです。
- アクセス修飾子を付けない
- 引数を取れない
- 型が最初に使用されたときに自動で1回だけ実行される
静的フィールドや静的プロパティの初期化が複雑な場合に利用します。
オブジェクト初期化子とコンストラクタの組み合わせ
オブジェクト初期化子の基本

C#のオブジェクト初期化子を使うと、コンストラクタで最低限の初期化を行い、残りを簡潔な構文で設定できます。
public class Mail
{
public string From { get; }
public string To { get; set; }
public string Subject { get; set; } = "";
public string Body { get; set; } = "";
// From だけは必須なのでコンストラクタ引数にする
public Mail(string from)
{
From = from;
}
}
public class Program
{
public static void Main()
{
// オブジェクト初期化子を使った初期化
var mail = new Mail("system@example.com")
{
To = "user@example.com",
Subject = "Welcome",
Body = "Thank you for registering."
};
Console.WriteLine($"From: {mail.From}, To: {mail.To}, Subj: {mail.Subject}");
}
}
From: system@example.com, To: user@example.com, Subj: Welcome
「必須項目はコンストラクタ」「任意項目はオブジェクト初期化子」という役割分担は、読みやすさと安全性のバランスがよい定番パターンです。
不変オブジェクト(イミュータブル)と初期化
イミュータブルクラスのコンストラクタ

イミュータブル(不変)クラスでは、コンストラクタで値を設定した後は状態を変更しません。
フィールドやプロパティをreadonlyやget専用にすることで実現します。
public class Point2D
{
public int X { get; }
public int Y { get; }
public Point2D(int x, int y)
{
X = x;
Y = y;
}
// 状態を変えず、新しいインスタンスを返すメソッド
public Point2D Move(int dx, int dy)
{
return new Point2D(X + dx, Y + dy);
}
}
このようにコンストラクタ以外では状態を変えない設計をすることで、並行処理時のバグを減らし、コードの予測可能性を高めることができます。
引数検証と防御的プログラミング
コンストラクタでのバリデーション

コンストラクタはインスタンス生成時の最後の砦とも言えるため、引数の検証を行っておくと安全です。
using System;
public class Person
{
public string Name { get; }
public int Age { get; }
public Person(string name, int age)
{
// 引数の検証
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Name must not be empty.", nameof(name));
}
if (age < 0 || age > 150)
{
throw new ArgumentOutOfRangeException(nameof(age), "Age must be between 0 and 150.");
}
// 正常な場合だけフィールドに設定
Name = name;
Age = age;
}
}
コンストラクタで不正なオブジェクトが生成されないようにしておくことで、後続の処理がシンプルになり、デバッグもしやすくなります。
よくある初期化パターンの比較
最後に、代表的なパターンを簡単に比較します。
| パターン | 概要 | メリット | 向いているケース |
|---|---|---|---|
| フィールド宣言時初期化 | フィールドに直接初期値 | 常に同じ既定値にしたい時にシンプル | 設定がほとんど変わらない小さなクラス |
| 通常コンストラクタ | 引数を受け取り内部を初期化 | 必須情報を確実に設定できる | ビジネスロジックの中心となるクラス |
| コンストラクタチェーン | 複数のコンストラクタを1つに集約 | 重複が減り保守性が高い | オーバーロードが多いクラス |
| オプション引数 | デフォルト値を引数で指定 | コンストラクタ数を減らせる | 引数数が少ない設定系クラス |
| 静的コンストラクタ | 型レベルの一度きりの初期化 | 静的メンバーの統一的な初期化 | 設定・キャッシュ・定数群 |
| オブジェクト初期化子併用 | 必須はコンストラクタ、任意は初期化子 | 可読性が高く柔軟 | DTOやViewModel、リクエストオブジェクトなど |
まとめ
コンストラクタでの初期化は、「どの情報を必須にするか」「どこまでを既定値にするか」「どこで検証するか」を明確にすることが重要です。
フィールド宣言時初期化、通常コンストラクタ、コンストラクタチェーン、オプション引数、静的コンストラクタ、オブジェクト初期化子などを組み合わせることで、読みやすく安全なクラス設計ができます。
プロジェクトの規模やチームのコーディング方針に合わせて、最も分かりやすい初期化パターンを選ぶように意識してみてください。
