継承は既存のクラスを土台にして、新しいクラスを作る仕組みです。
重複コードを減らし、共通のふるまいを1か所に集約できます。
この記事では、C#における継承の考え方と基本的な書き方を、基底クラスと派生クラスの定義からインスタンス化、実行まで順序だてて解説します。
C#の継承とは(初心者向け)
継承の基本(基底クラスと派生クラス)
継承は、あるクラスの性質を別のクラスが引き継ぐことを指します。
引き継がれる元のクラスを基底クラス、引き継いで新しく拡張したクラスを派生クラスと呼びます。
継承はis-a関係を表し、例えば「学生は人である」のように、派生クラスは基底クラスの一種であると説明できます。
以下の用語を把握しておくと理解が進みます。
| 用語 | 役割 | 例 |
|---|---|---|
| 基底クラス | 共通の性質や動作を定義する土台 | Person |
| 派生クラス | 基底クラスを拡張し新しい機能を追加 | Student : Person |
| 継承 | 既存の機能を再利用し拡張する仕組み | class Student : Person |
| is-a関係 | 派生は基底の一種 | Student is a Person |
派生クラスは基底クラスのメンバーに自然にアクセスできるため、共通化と再利用がスムーズになります。
使いどころ(再利用と共通化)
継承は、プロジェクト内に「共通する性質やふるまい」がある場合に効果を発揮します。
例えばユーザー、管理者、ゲストが共通して持つ名前や表示処理を基底クラスにまとめ、個別の追加機能を派生クラスに分けることで、コードの重複を取り除けます。
結果として、修正箇所が少なくなり保守性が上がる点が大きな利点です。
C#の書き方の形(class 派生 : 基底)
C#では継承をコロンで表します。
形は「class 派生 : 基底」です。
コロンの左が新しく作る派生クラス、右が継承元の基底クラスです。
// もっともシンプルな継承の形
class Animal
{
// Animalが持つ共通の性質や振る舞いを定義
}
class Dog : Animal
{
// Animalを引き継ぎ、Dogならではの機能を追加
}
DogはAnimalの一種という関係を、そのままコードで表現していると捉えると理解しやすくなります。
基底クラスの作り方
共通プロパティとメソッドをまとめる
基底クラスには、複数の派生クラスで共有したいプロパティやメソッドを集約します。
例えば、人を表すクラスならNameのような共通プロパティと、自己紹介のような共通メソッドを持たせます。
将来の拡張を見越して、差し替えの可能性があるメソッドはvirtualで宣言しておくと、派生側で自由に上書きできます。
シンプルな例(最小構成)
以下は、人を表す基底クラスの最小構成です。
ここでは初心者が扱いやすいように、パラメータなしのコンストラクタを用意し、デフォルト値を設定しています。
// 基底クラス: Person
public class Person
{
// 共通プロパティ: 名前
public string Name { get; set; }
// コンストラクタで初期値をセット
public Person()
{
Name = "不明"; // 既定値
}
// 共通の説明を返すメソッド
// 派生クラスで振る舞いを差し替えたいのでvirtualにしています
public virtual string Describe()
{
return $"名前: {Name}";
}
}
共通の情報はPersonに集めることで、派生クラスは追加の違いだけに集中できます。
派生クラスの作り方と使い方
class Derived : Base の定義
派生クラスはclass Derived : Baseの形で宣言します。
以下では学生を表すStudentが、人を表すPersonを継承します。
// 派生クラス: Student は Person を継承
public class Student : Person
{
// Student固有のプロパティを追加
public int Grade { get; set; } // 学年
// 既定の学年をセットするコンストラクタ
public Student()
{
Grade = 1;
}
// PersonのDescribeを拡張して、学年情報も含める
public override string Describe()
{
// 基底の共通情報に、派生の特有情報を足す
return $"名前: {Name}, 学年: {Grade}年";
}
// Studentだけが持つふるまい
public void Study()
{
// 学習処理のイメージ
System.Console.WriteLine($"{Name}は勉強しています。");
}
}
共通は継承、違いは追加という構成にすることで、役割が明確になり、コードを読みやすく保てます。
機能を追加して拡張する
派生クラスでは2つの拡張方法があります。
1つは新しいプロパティやメソッドを追加する方法で、もう1つは基底のvirtualメソッドをoverrideで差し替える方法です。
前者は機能の「追加」、後者は同じ入口での「振る舞いの切り替え」に向いています。
むやみに差し替えるより、差し替える必然性があるメソッドだけをvirtualにすると、設計が落ち着きます。
派生クラスのインスタンスを使う
ここまでの基底クラスと派生クラスを使って、実行してみます。
インスタンスを生成してプロパティを設定し、メソッドを呼び出す流れです。
using System;
// 実行用のサンプル全体
public class Person
{
public string Name { get; set; }
public Person()
{
Name = "不明";
}
public virtual string Describe()
{
return $"名前: {Name}";
}
}
public class Student : Person
{
public int Grade { get; set; }
public Student()
{
Grade = 1;
}
public override string Describe()
{
// PersonのDescribeを上書きして学年も表示
return $"名前: {Name}, 学年: {Grade}年";
}
public void Study()
{
Console.WriteLine($"{Name}は勉強しています。");
}
}
public class Program
{
public static void Main()
{
// Personのインスタンスを生成し、プロパティに値をセット
var p = new Person();
p.Name = "Taro";
Console.WriteLine(p.Describe()); // => 名前: Taro
// Studentのインスタンスを生成し、固有の情報も設定
var s = new Student();
s.Name = "Hanako";
s.Grade = 3;
Console.WriteLine(s.Describe()); // => 名前: Hanako, 学年: 3年
// Studentだけが持つメソッド
s.Study();
// 参照を基底型に置き換えても、overrideした振る舞いが呼ばれる
Person asPerson = s; // Student is a Person
Console.WriteLine(asPerson.Describe()); // => StudentのDescribeが呼ばれる
}
}
名前: Taro
名前: Hanako, 学年: 3年
Hanakoは勉強しています。
名前: Hanako, 学年: 3年
基底型の変数に代入してもoverrideしたメソッドが呼ばれるため、共通の窓口から適切な動作に切り替えられます。
これが継承と相性の良い「動作の使い分け」の基本です。
継承の注意点とコツ
無理な継承は避ける(シンプルに)
継承は便利ですが、is-a関係でないのに継承を使うのは避けるべきです。
継承におけるis-a関係とは、「サブクラスはスーパークラスの一種である」という関係性を表すものです。
例えば、「犬」クラスが「動物」クラスを継承する場合、「犬 is-a 動物」という関係が成り立ち、犬は動物が持つ機能や特性をすべて受け継ぎます。この関係により、サブクラスはスーパークラスのより具体的で専門的なバージョンとして振る舞うことができます。
例えば「注文は人である」ではないため、そこに継承は成り立ちません。
迷ったら共通化の範囲を狭めて、後から必要なときに広げるくらいが安全です。
名前と責務を分かりやすく
クラス名は「何者か」を、メソッド名は「何をするか」を素直に表すと読みやすくなります。
基底クラスは「全員に本当に必要なものだけ」を置き、派生クラスは「違い」を担当させます。
共通の判断基準は、別の派生クラスにもそのメンバーが自然に当てはまるかです。
よくあるミスと対策(基本のみ)
- 基底に何でも入れすぎる: 本当に全員に必要な最小限に絞ります。不要なメンバーは派生に移すか、後から追加します。
- 差し替え不要なのにvirtualにする: 変更点が明確なメソッドだけ
virtual化し、他は通常メソッドのままにします。 - 派生で上書きし忘れる: 仕様で差し替える前提のメソッドは、テストで動作を確認し、必要に応じて派生で
overrideします。 - 命名と責務が曖昧: クラス名と役割を1文で説明できるかを確認し、説明できない場合は分割や再設計を検討します。
まとめ
継承は、共通の性質を1か所に集め、違いを派生で表現するための基本手法です。
書き方はclass 派生 : 基底というシンプルな形で、プロパティやメソッドを自然に引き継げます。
必要に応じてvirtualとoverrideを使えば、共通の入口から適切な動作に切り替えられます。
まずは小さな基底クラスを作り、派生で最小限の拡張を行うところから始めると、無理のない設計で継承の効果を実感できます。
