オブジェクト初期化の多様性
クラスや構造体を設計する際、インスタンスの生成方法(初期化のパターン)を複数提供したい場合があります。例えば、すべての値を個別に指定して生成する場合もあれば、一つの値を基準に残りを自動設定して生成する場合もあります。
C#では、引数の構成が異なるコンストラクタを複数定義する「オーバーロード」と、あるコンストラクタから別のコンストラクタを呼び出す「コンストラクタ初期化子(: this(...))」を組み合わせることで、柔軟かつ保守性の高い初期化ロジックを実装できます。
この記事では、コンストラクタのオーバーロードの基本と、重複コードを防ぐためのコンストラクタ連鎖について解説します。
コンストラクタのオーバーロード
メソッドと同様に、コンストラクタも引数の数や型が異なれば、同じクラス内に複数定義することができます。
例えば、長方形を表すRectangle構造体を考えます。
- 幅と高さを個別に指定する(長方形)
- 1辺の長さだけを指定する(正方形)
この2つのパターンに対応するために、2つのコンストラクタを定義します。
thisキーワードによるコンストラクタの連鎖
複数のコンストラクタを定義する際、それぞれの内部でフィールドへの代入処理を個別に記述すると、コードが重複し、修正漏れの原因となります。
これを防ぐために、thisキーワードを使用して、あるコンストラクタから「メインとなるコンストラクタ」を呼び出す手法が推奨されます。これを「コンストラクタの連鎖(Constructor Chaining)」と呼びます。
構文: public ClassName(args) : this(mainArgs)
コード例:長方形と正方形の初期化
以下の例では、幅と高さを指定する「メインのコンストラクタ」を定義し、正方形用のコンストラクタからそれを呼び出しています。
using System;
public class Program
{
public static void Main()
{
// 1. 幅と高さを指定して初期化 (メインコンストラクタ)
var rect = new Rectangle(10, 20);
Console.WriteLine($"長方形: 幅={rect.Width}, 高さ={rect.Height}");
// 2. 1辺の長さを指定して初期化 (連鎖コンストラクタ)
// 内部で Rectangle(5, 5) が呼び出される
var square = new Rectangle(5);
Console.WriteLine($"正方形: 幅={square.Width}, 高さ={square.Height}");
}
}
/// <summary>
/// 長方形を表す読み取り専用構造体
/// </summary>
public readonly struct Rectangle
{
public int Width { get; }
public int Height { get; }
// --- メインのコンストラクタ ---
// すべてのプロパティを初期化する責任を持つ
public Rectangle(int width, int height)
{
Width = width;
Height = height;
}
// --- オーバーロードされたコンストラクタ ---
// 正方形の場合、幅と高さは同じ値になる。
// : this(size, size) と記述することで、
// 上記のメインコンストラクタ Rectangle(int, int) に処理を委譲する。
public Rectangle(int size) : this(size, size)
{
// ここには追加の初期化ロジックがあれば記述する。
// 基本的な代入はメインコンストラクタで行われているため、空でもよい。
}
}
出力結果:
長方形: 幅=10, 高さ=20
正方形: 幅=5, 高さ=5
オプション引数との比較
C# 4.0以降では「オプション引数(デフォルト引数)」が利用可能になったため、単純な初期化であればオーバーロードを使わずに1つのコンストラクタで完結させることも可能です。
// オプション引数を使用した例
public Rectangle(int width, int height = 0)
{
// height が省略された場合のロジックなどが必要になる場合がある
Width = width;
Height = (height == 0) ? width : height;
}
しかし、引数の意味が大きく変わる場合(例: intでIDを指定する場合と、stringで名前を指定する場合など)や、引数の組み合わせが複雑な場合は、thisを使用した明示的なオーバーロードの方が、コードの意図が明確になり、可読性が高まります。
まとめ
コンストラクタのオーバーロードとthisキーワードによる連鎖は、クラスの初期化ロジックを一箇所(メインコンストラクタ)に集約するための重要なテクニックです。
- オーバーロード: 異なる引数パターンでインスタンス生成を可能にします。
thisによる連鎖: 共通の初期化処理を再利用し、コードの重複(DRY原則違反)を防ぎます。
これにより、利用者は柔軟にオブジェクトを生成でき、開発者は保守性の高いコードを維持できます。
