1. はじめに
例えば、Personクラスがあるとしよう:
public class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
これでほぼ天才になった気分だよね ― どんな名前や年齢でも人を作れる。でも困ったことに、時々情報が足りないこともある!アプリのユーザーが名前だけ入力して、年齢は後で分かる場合とか。あるいは、デフォルトの「標準」年齢を設定したい時もある。もちろん色々な回避策は考えられるけど、C#はこの問題にエレガントな解決策 ― コンストラクタのオーバーロード ― を用意してくれてるんだ。
コンストラクタのオーバーロードっていうのは、1つのクラスに同じ名前(クラス名)のコンストラクタが複数あって、パラメータリストが違うってこと。
そのリストの違いって何? 例えば:
- パラメータの数: 例えば、1つは2個、もう1つは3個。
- パラメータの型: 1つは(string, int)、もう1つは(string, decimal)。
- パラメータの順番: (string, int)と(int, string)は別物。
一番大事なルール:コンパイラはシグネチャでコンストラクタ(やメソッド)を区別する。シグネチャはコンストラクタ(またはメソッド)の名前とパラメータリスト(数・型・順番)で決まる。アクセス修飾子(publicやprivate)や、普通のメソッドなら戻り値の型はシグネチャに含まれない。コンストラクタには戻り値がなく、名前は必ずクラス名と同じだよ。
2. コンストラクタのオーバーロード:構文と例
public class Cat
{
public string Name;
public string Color;
// パラメータなしコンストラクタ
public Cat()
{
Name = "名前なしネコ";
Color = "グレー";
}
// 1つのパラメータ
public Cat(string name)
{
Name = name;
Color = "グレー";
}
// 2つのパラメータ
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
使い方:
Cat barsik = new Cat(); // "名前なしネコ", "グレー"
Cat murzik = new Cat("ムルジク"); // "ムルジク", "グレー"
Cat ryzhik = new Cat("リジク", "オレンジ"); // "リジク", "オレンジ"
オブジェクト作成時にパラメータを指定すると、C#はその数と型からどのコンストラクタを使うか「当てて」くれる。
めっちゃ便利!クラスのユーザーは知らないパラメータを無理に指定しなくていい。とりあえずネコが欲しいならパラメータなし、名前だけ分かってるならもう1つ、色まで指定したいなら3つ目のやつ。
3. オーバーロードされたコンストラクタの呼び出しはどう動く?
C#は自動的に、渡された引数の型と数から適切なコンストラクタをコンパイル時に選んでくれる。間違えたらすぐコンパイラが怒ってくれるから安心。
int型のパラメータのコンストラクタを追加してみよう:
public Cat(int age)
{
Name = "名前なしネコ";
Color = "グレー";
// 年齢用の追加コード
}
これで「年齢指定の子猫」も作れる:
Cat oldCat = new Cat(5); // Cat(int age)が呼ばれる
| コンストラクタ呼び出し | どれが呼ばれる? |
|---|---|
|
パラメータなし |
|
string型1つのパラメータ |
|
string型2つのパラメータ |
|
int型1つのパラメータ |
4. コンストラクタ内部の呼び出し:thisキーワード
いくつかのコンストラクタが似たような(または同じ)処理をすることがある。コードをコピペしないために、「あるコンストラクタから別のコンストラクタを呼ぶ」ことができる。その時に使うのがthisキーワード。
public class Cat
{
public string Name;
public string Color;
public Cat() : this("名前なしネコ")
{
// このコンストラクタはCat(string name)を呼ぶ
}
public Cat(string name) : this(name, "グレー")
{
// このコンストラクタはCat(string name, string color)を呼ぶ
}
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
結果として:
- new Cat()はCat(string name)を呼び、さらにCat(string name, string color)を呼ぶ。
- 全部の「道」はローマに通じる:つまり、初期化処理は1つの「メイン」コンストラクタに集約される。
このやり方はconstructor chaining(コンストラクタチェーン)って呼ばれてるよ。
5. 実生活の例
ゲーム用のヒーロークラス(Hero)を作ってみよう。名前とレベルがあるやつね!こんな感じ:
1つのコンストラクタ:
public class Hero
{
public string Name;
public int Level;
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
これを色んな作り方で作れるようにしよう!
たくさんのコンストラクタ:
public class Hero
{
public string Name;
public int Level;
// 何も指定しなければ「名前なし・レベル1」
public Hero() : this("名前なし", 1)
{
}
// 名前だけ分かってる場合、レベルはデフォルトで1
public Hero(string name) : this(name, 1)
{
}
// 一番メインのコンストラクタ
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
これでヒーローを色んな方法で作れる:
Hero h1 = new Hero(); // "名前なし", 1
Hero h2 = new Hero("アルトゥール"); // "アルトゥール", 1
Hero h3 = new Hero("ローラ", 10); // "ローラ", 10
6. 実装の細かい話:落とし穴と注意点
まず:自分で1つでもコンストラクタを宣言したら、コンパイラは「空の」デフォルトコンストラクタを自動生成しない。だから、パラメータ付きのコンストラクタだけ書いて、パラメータなしを忘れると、こんな呼び出しは:
Hero h = new Hero(); // パラメータなしコンストラクタがなければエラー!
コンパイルエラーになるよ。
次に:チェーン呼び出しで間違ったコンストラクタを呼ぶと、初期化ロジックが崩れることがある。ちゃんと一番「完全な」コンストラクタで全部の処理をやって、他はthis(...)でそこにデータを渡すのがベスト。
さらに:パラメータが型だけ違う場合(例:Cat(string s)とCat(object o))、nullを渡すとどっちが呼ばれるか分かりにくい。コンパイラも迷うことがあるから注意!
7. オーバーロードとフィールドの初期化
クラスには必ず設定したいフィールドがあることが多い。コンストラクタのオーバーロードで便利にできるけど、どんなデフォルト値が妥当かは自分で決めてね。
チェック付きの例:
public class Book
{
public string Title;
public int Year;
// デフォルトは「タイトルなし」、2000年
public Book() : this("タイトルなし", 2000)
{
}
public Book(string title) : this(title, 2000)
{
}
public Book(string title, int year)
{
Title = title;
Year = year;
}
}
8. パラメータセットの違うオーバーロード
コンストラクタが何十個もあるクラスもある!それが良い設計とは限らない(「設定用」メソッドや「ビルダー」パターンを使った方がいい場合もある)が、ユーザー向けのオブジェクトやモデル、DTOなら全然アリ。
例:「ペット」クラス。全部の特徴を指定できるし、必要最低限だけでもOK。
public class Pet
{
public string Name;
public int Age;
public string Type;
public bool IsVaccinated;
// デフォルトコンストラクタ
public Pet() : this("NoName", 0, "Cat", false) { }
// 最低限の情報
public Pet(string name, string type) : this(name, 0, type, false) { }
// フルコンストラクタ
public Pet(string name, int age, string type, bool isVaccinated)
{
Name = name;
Age = age;
Type = type;
IsVaccinated = isVaccinated;
}
}
メソッドのオーバーロードとコンストラクタの違い
| 普通のメソッド | コンストラクタ(オーバーロード含む) |
|---|---|
| いつでも呼び出せる | オブジェクト作成時(new)だけ呼ばれる |
| どんな型でも値を返せる | 値は絶対返さない(型は書かない) |
| メソッド名は自由、動詞が多い | 名前は必ずクラス名と同じ |
| パラメータでオーバーロードできる | 同じくパラメータでオーバーロードできる |
GO TO FULL VERSION