1. はじめに
このレクチャーでは、C#で継承のシンタックスがどう動くかを見ていくよ。派生クラスの宣言方法、baseキーワードって何?どうやって親クラスにアクセスするの?ってところを理解しよう。これを知っておくと、コードを書くのがめっちゃ楽になるし、プログラムももっとエレガントになるよ(しかもコピペミスを直す手間も減るから、ちょっと寝坊しても大丈夫かもね)。
例えば、こんな感じ:Vehicle(乗り物)クラスがあるとする。これはベースで、汎用的な設計図だよ。すべての乗り物は走れるし、色やメーカーとかも持ってるよね。
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public void Drive()
{
Console.WriteLine("走ろう!");
}
}
じゃあ、新しい乗り物タイプ、つまり車を追加したいとする。車も乗り物だけど、ドアの数みたいな特徴があるよね。色やメーカーのことをまた書くのは変だよね、もう定義されてるし!
継承を使えば、「車は乗り物だけど、いろいろ追加機能があるよ」って言えるんだ。
2. 子クラスの宣言方法
C#で継承クラスを作るには、コロン:を使うよ。クラス名の後ろに、どのクラスを継承するか書くんだ。
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
}
こう読む:クラスCarはVehicleクラスのフィールドやメソッドを全部継承して、さらに自分だけの特徴(例えばドアの数)を追加してるよ。
注意:C#では単一継承しかできない(つまり、親クラスは一つだけ)。でもinterfaceはいくつでもOK!(これはまた今度話すね)。
どう動くの?
例えば、こう宣言したら:
Car myCar = new Car();
...このmyCarオブジェクトはVehicleのフィールドやメソッドにも、自分のプロパティにもアクセスできるよ。例:
myCar.Brand = "Toyota";
myCar.Color = "青";
myCar.NumberOfDoors = 4;
myCar.Drive(); // 継承したメソッド
リアルなシナリオ:
「進化する」アプリで、ユーザーが乗り物タイプを選んで、その特徴を全部表示するメニューを作れるよ。新しい乗り物タイプを追加しても、書くコードは最小限!
3. コンストラクタの継承
派生クラスのオブジェクトを作るとき、ベースクラスのプロパティもコンストラクタで初期化したいことがあるよね。例えば、ブランドや色を絶対に指定したい場合とか。
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public Vehicle(string brand, string color)
{
Brand = brand;
Color = color;
}
}
この場合、継承クラスのコンストラクタでベースクラスのコンストラクタを呼ぶ必要がある!そのためにbaseキーワードを使うよ:
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color) // Vehicleのコンストラクタを呼ぶ!
{
NumberOfDoors = numberOfDoors;
}
}
実際どうなる?
Car bmw = new Car("BMW", "黒", 4);
Console.WriteLine($"{bmw.Brand}, {bmw.Color}, ドア数: {bmw.NumberOfDoors}");
bmw.Drive(); // まだ使える!
つまりbmwは車でもあり、同時に乗り物でもあるってこと。親のこともちゃんと覚えてるよ。
4. baseとthisの違い
C#にはクラスメンバーを扱うためのキーワードが2つある:thisとbase。
- this — 今のオブジェクト(自分自身のフィールドやメソッド)へのアクセス。
- base — ベースクラスのメンバー(つまり親)へのアクセス。
baseを使うタイミングは?例えば、ベースクラスのメソッドのロジックを拡張したいときや、派生クラスからベースクラスを初期化したいとき。例えば:
public class Bicycle : Vehicle
{
public int NumberOfGears { get; set; }
public Bicycle(string brand, string color, int gears)
: base(brand, color) // ベースクラスのコンストラクタを呼ぶ
{
NumberOfGears = gears;
}
public void ShowInfo()
{
// ベースクラスのプロパティを使う!
Console.WriteLine($"{Brand} ({Color}), ギア数: {NumberOfGears}");
}
}
5. メソッド内でのbaseキーワード
baseはコンストラクタだけじゃない!ベースクラスのメソッドの動作を変えたいけど、一部のロジックはそのまま使いたいときにも使えるよ。
例えば、CarクラスのDriveメソッドで、どの車を運転してるかも表示したい場合:
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color)
{
NumberOfDoors = numberOfDoors;
}
public void DriveCar()
{
base.Drive(); // ベースの実装を呼ぶ
Console.WriteLine($"{Brand}を{NumberOfDoors}ドアで運転中!");
}
}
解説:
base.Drive();の呼び出しは、「お父さん、まず自分の話をして、そのあと僕が詳細を追加するね!」って感じ。DriveCar()を呼ぶと、こう表示される:
走ろう!
BMWを4ドアで運転中!
メソッドのオーバーライド(override)については、また次のレクチャーで話すよ :P
6. 継承の図
クラス同士の関係をビジュアルで見てみよう。簡単な図だよ(UMLは朝見ると怖いからね!):
Vehicle(ベース)
/ \
Car Bicycle
- すべての矢印は派生クラスからベースクラスへ向かう。
- すべての子クラスはpublic/protectedなベースクラスのメンバーにアクセスできる。
public、protected、privateの違い
ちょっとおさらいしよう。
派生クラスで見えるのは:
| 修飾子 | 子クラスでの可視性 |
|---|---|
| public | はい |
| protected | はい |
| private | いいえ |
よくあるミス:
派生クラスからprivateフィールドにアクセスしようとすること:
public class Vehicle
{
private string secret = "誰にもバレない!";
}
public class Car : Vehicle
{
public void RevealSecret()
{
Console.WriteLine(secret); // エラー!見えない。
}
}
ベースクラス vs. 派生クラスの比較
| Vehicle | Car(派生) | |
|---|---|---|
| Brand | あり | 継承 |
| Color | あり | 継承 |
| Drive() | あり | 継承(または拡張) |
| NumberOfDoors | なし | 独自(ユニークなプロパティ) |
7. 例
もし「乗り物管理」みたいなシンプルなアプリを作り続けるなら、こんな感じで構成できるよ:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public Vehicle(string brand, string color)
{
Brand = brand;
Color = color;
}
public void Drive()
{
Console.WriteLine($"{Brand} {Color}が走った!");
}
}
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color)
{
NumberOfDoors = numberOfDoors;
}
public void ShowCar()
{
Console.WriteLine($"ブランド: {Brand}, 色: {Color}, ドア数: {NumberOfDoors}");
}
}
public class Bicycle : Vehicle
{
public int NumberOfGears { get; set; }
public Bicycle(string brand, string color, int numberOfGears)
: base(brand, color)
{
NumberOfGears = numberOfGears;
}
}
メインメソッドで乗り物リストを作って、いろんな方法で説明できるよ:
Car ford = new Car("Ford", "赤", 4);
ford.ShowCar();
ford.Drive();
Bicycle trek = new Bicycle("Trek", "緑", 21);
trek.Drive();
8. ミス・特徴・ちょっとしたジョーク
めっちゃよくあるミスは、派生クラスでベースのコンストラクタを呼び忘れること。もしVehicleにパラメータなしのコンストラクタがなくて、base(...)を呼ばなかったら、コンパイルエラーになるよ。ベースを初期化できないって怒られる。これは1階がないのに2階を建てようとするようなもの。無理だよね。ちゃんと上にデータを渡そう。
2つ目のミス:ベースクラスのフィールドやメソッドがprivateで宣言されてると、派生クラスからは見えないことを忘れること。「子供」に残したいものはprotectedを使おう(これについてはまた次のレクチャーで)。
そして大事なのは:継承は便利だけど、複雑な階層を作りすぎないこと。現実世界でも、継承チェーンが2~3段以上になったら、たぶん何か間違ってる。誰が誰から継承してるのか、何のためにやってるのか、すぐに混乱しちゃうからね。
GO TO FULL VERSION