1. はじめに
プログラミングにおける継承は、現実世界の「遺伝」とすごく似てるんだ。たとえば、君は親から目の色や鼻の形、絵の才能なんかを「受け継ぐ」ことができるよね。これらの特徴は「デフォルトで」もらえるから、ゼロから作る必要はない。でも、自分だけのユニークな特徴を発展させることもできるし、それは親にはないものだったりする。
プログラミングでも、だいたい同じ感じで動くよ:
- あるクラス(これを基底クラスとか親クラス、たまにスーパークラスって呼ぶ)は、すべての「子孫」が持つ共通の特徴(プロパティ)や振る舞い(メソッド)を定義する。
- 別のクラス(派生クラスとか子クラス、たまにサブクラスとも呼ぶ)は、親からこれらの共通の特徴を継承する。つまり、publicやprotectedなプロパティやメソッドは自動的に全部もらえる。再宣言する必要はない。
- そのうえで、派生クラスは自分だけのユニークなプロパティやメソッドを追加できる。親クラスにはないやつね。
- たまに、派生クラスは継承したメソッドの振る舞いを変更することもできる(これは次の講義でやるけど、ポリモーフィズムって呼ばれるやつ)。
継承のキーメッセージは「〜である」(is-a relationship)ってやつ。たとえば、君が魔法使いや戦士、アーチャーが出てくるゲームを作るとしよう:
- 戦士は〜であるキャラクター。
- 魔法使いは〜であるキャラクター。
- アーチャーは〜であるキャラクター。
もし戦士がキャラクターなら、キャラクターが持ってるものは全部持ってるべきだし、さらに戦士だけのユニークなものも持てるってこと!
2. 継承の構文
例を見て、ちょっとコードを書いてみよう。
構文の基本
// 基底クラス
public class Animal
{
public string Name { get; set; }
public void Move()
{
Console.WriteLine($"{Name} は動いている。");
}
}
// 子クラス
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine($"{Name} は吠える: ワン!");
}
}
Dogクラス名の後ろのコロンに注目してみて。ここで「DogはAnimalのすべてを継承するよ」って宣言してるんだ。
Animalが持ってるpublicやprotectedなものは全部Dogにも現れる!
継承の使い方
このクラスたちをコンソールアプリで使ってみよう:
var dog = new Dog();
dog.Name = "シャリク"; // NameプロパティはAnimalから継承
dog.Move(); // MoveメソッドもAnimalから継承
dog.Bark(); // Dog独自のメソッド
// 出力:
// シャリク は動いている。
// シャリク は吠える: ワン!
「動物園」をちょっと賑やかにするために猫も作ってみよう:
public class Cat : Animal
{
public void Meow()
{
Console.WriteLine($"{Name} は鳴く: ニャー!");
}
}
var cat = new Cat();
cat.Name = "ムルカ";
cat.Move();
cat.Meow();
3. 継承で手間を省く方法
似たようなものがいくつも出てきたとき、毎回コードをコピペする必要はない。継承は「マスターコピー」みたいなもので、一度ロジックを書けば、全部の子孫がそれを受け継げるんだ。
ビジュアル化:継承ツリー
. Animal
/ \
Dog Cat
| クラス | Nameプロパティ | Moveメソッド | 独自メソッド |
|---|---|---|---|
| Animal | + | + | - |
| Dog | + | + | Bark() |
| Cat | + | + | Meow() |
もう一つの例 ― アプリ拡張
いろんなタイプの人を管理するシステムを作りたいとしよう。昔ながらのやり方を思い出してみて:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
// ここで従業員を追加
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Position { get; set; }
}
ちょっと面倒だよね!名前のデータが重複してる。継承を使えばこうなる:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Employee : Person
{
public string Position { get; set; }
}
public class Student : Person
{
public int Grade { get; set; }
}
これでEmployeeもStudentもFirstNameとLastNameを持ってるから、2回書く必要なし。いろんな役割を簡単に拡張できるイメージだね。
これが継承のマジック!共通の特徴を基底クラスで一度だけ書いて、派生クラスで機能を「拡張」して、個別のディテールを追加できるんだ。
4. 継承のメリット
コードの再利用性 (Code Reusability): これが一番分かりやすいメリット。何度も同じコードを書く必要がない。一度書けば何度でも使える。コードがスッキリしてキレイになる(プログラミング界では「Don't Repeat Yourself」― DRY、「繰り返すな」って言うよ)。
メンテナンスが楽 (Easier Maintenance): もし移動ロジックを変えたいなら、基底クラスAnimalだけ直せばOK。全部の派生クラスが自動でその変更を受け取る。大きなプロジェクトだと、これでめっちゃ時間が節約できる!
階層構造の作成 (Creating Hierarchies): 継承を使うと、現実世界の「〜である」関係をモデル化できて、分かりやすい構造を作れる。戦士はキャラクター、魔法使いもキャラクター。コードが整理されて、理解しやすくなるよ。
ポリモーフィズムの基礎: まだポリモーフィズムは詳しくやってないけど、継承はその土台。ポリモーフィズムがあると、基底クラスの共通インターフェースでいろんな派生クラスのオブジェクトを扱える。たとえば、Animalのリスト(DogでもCatでも他の動物でもOK)を作って、Moveメソッドを呼べる。どの動物か気にしなくていい。これ、めっちゃ強力だから、また後でやろう!
5. 継承の細かいポイント
C#の単一継承: 他の言語(たとえばC++)と違って、C#は単一継承しかできない。つまり、クラスは一つの基底クラスからしか継承できない。Animalと、たとえばVehicle(もしそんなクラスがあったとして)を同時に継承することはできないよ。
すべてのクラスは暗黙的にobjectを継承: これ、面白い豆知識!もし基底クラスを明示的に指定しなかったら、君のクラスは自動的にSystem.Objectクラスを継承する。これは.NETで一番基本のクラスで、ToString()、Equals()、GetHashCode()みたいな基本メソッドを持ってる。だから、ToString()をオーバーライドするときは、実はobjectから継承したメソッドを上書きしてるってこと!
コンストラクタは継承されない: 派生クラスは基底クラスのコンストラクタを継承しない。派生クラスで明示的にコンストラクタを書く必要がある。ただし、基底クラスに引数付きコンストラクタがある場合は、派生クラスのコンストラクタから必ず基底クラスのコンストラクタを呼ばなきゃいけない。それにはbaseキーワードを使うよ。
privateメンバーはアクセスできない: privateな基底クラスのメンバーは、派生クラスから直接アクセスできない。オブジェクトの中には存在してるけど、派生クラスのコードから名前で呼び出すことはできない。もし派生クラスだけに見せたいなら、protectedアクセス修飾子を使おう。
6. 階層のビジュアル化
継承関係をもっと分かりやすくするために、よく図を使うよ。代表的なのがUML(統一モデリング言語)のクラス図。動物の例だと、こんな感じになる:
classDiagram
class Animal {
+ string Name
+ Move()
}
class Dog {
+ Bark()
}
class Cat {
+ Meow()
}
Animal <|-- Dog : inherits
Animal <|-- Cat : inherits
この図で、塗りつぶされてない三角の矢印(DogからAnimal、CatからAnimal)は「〜である」(is-a relationship)という継承関係を表してるよ。
7. 継承の実践的な使い道
継承は単なるお勉強用の概念じゃなくて、現代プログラミングの柱の一つで、実際のプロジェクトでもめっちゃ使われてる:
グラフィカルユーザーインターフェース(GUI)開発: WPF、WinForms、MAUI(.NETのデスクトップアプリ用フレームワーク)では、ほとんどすべてのコントロール(ボタン、テキストボックス、ウィンドウなど)が共通の基底クラスControlやUIElementを継承してる。だから、サイズや位置、表示/非表示みたいな共通プロパティや、イベント処理みたいな共通メソッドを持てるんだ。
たとえば、ButtonはControl、TextBoxもControlって感じ。
ゲームエンジン: 継承はゲームオブジェクトの階層を作るのにピッタリ:GameObject → Character → Player/Enemyとか、Vehicle → Car/Motorcycleみたいな感じ。
データベース操作(ORM): Entity Framework Coreみたいなフレームワークでは、すべてのエンティティの基底クラス(たとえばBaseEntity)を定義して、Id(DBのID)やCreatedAt(作成日時)みたいなプロパティを持たせることが多い。具体的なエンティティ(User、Product、Orderなど)はBaseEntityを継承して、これらのプロパティを自動的に持てる。
テスト: テストフレームワーク(xUnitやNUnitなど)でも継承が使われてて、共通の初期化やクリーンアップロジックを持つ基底テストクラスを作って、そこから具体的なテストクラスを継承できる。
ライブラリやフレームワーク: .NETの標準ライブラリ自体も継承をめっちゃ使ってる。たとえば、多くのコレクションや型は共通の基底クラスを継承したり、共通のインターフェースを実装してる(これはまた後でやるよ)。
C#や.NETの面接では、継承やその原則(DRY、is-a)や、他の概念(たとえばコンポジションやインターフェース)との違いについての質問がめっちゃよく出る。説明できるだけじゃなくて、実際のコードや現実の例を挙げられると超強い!
ね、継承ってただの「言語の機能」じゃなくて、コードを整理したり、重複を減らしたり、論理的につながったシステムを作るための強力なツールなんだ。スケーラブルでメンテしやすいコードを書くカギだよ!
GO TO FULL VERSION