C#入門:継承(Inheritance)とは何か?再利用可能なコードへの第一歩

以下は、C#における継承(Inheritance)について解説する、初学者向けの技術ブログ記事です。クラス設計の基本を学び、再利用性と保守性を高めるコードを書く第一歩としてご活用ください。


1. はじめに

オブジェクト指向プログラミングの重要な概念の1つが「継承」です。C#でも、あるクラス(親クラス・基底クラス)の機能を別のクラス(子クラス・派生クラス)が受け継ぐことができます。

この記事では以下を解説します:

  • 継承とは何か
  • 基本的な書き方
  • メリットと注意点
  • 実例コード
  • 練習問題

2. 継承とは?

継承(Inheritance)とは、あるクラス(基底クラス)のプロパティやメソッドを、新しいクラス(派生クラス)が「引き継ぐ」仕組みです。

✅ 再利用性が高まり、共通の処理をまとめて管理しやすくなります。

Animalが基本クラス、DogやCatが派生クラス


3. 基本構文

InheritanceSampleプロジェクトとして作成しましょう

class Animal
{
    public void Eat()
    {
        Console.WriteLine("食べています");
    }
}

class Dog : Animal
{
    public void Bark()
    {
        Console.WriteLine("ワンワン!");
    }
}

上記の例では、DogクラスがAnimalクラスを継承しています。よって、Dogクラスのインスタンスは、Eat()メソッドも利用可能です。

今回のように

  • Animal → Dog という 下向きの継承矢印
  • 並んでいるクラスの横に、それぞれの絵と動作を描いた構成

は、初学者にとって非常に直感的でわかりやすい図です。

理由:

  1. 上下関係=階層構造のイメージと一致 →「上が親クラス」「下が子クラス」という物理的なレイアウトは自然に理解されやすいです。
  2. 矢印で「DogはAnimalの一種」という方向を明示 → Dog は Animal から「機能を受け継ぐ」と直感的に読み取れます。
  3. 動作の違い(EatとBark)も視覚で表現 → クラスの違いと機能の違いが視覚的に一目瞭然です。

もし変えるとしたら…

  • UML的な形式では逆向き(上向き)の矢印 ▲ が使われますが、それは中上級者向けです。
  • 初学者には「親→子」の流れのほうが 感覚的にしっくりくる 場合が多いです。

結論:

今回の「下向き矢印」は初学者に最適な表現です。

var dog = new Dog();
dog.Eat();   // → 食べています
dog.Bark();  // → ワンワン!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritanceSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var dog = new Dog();
            dog.Eat();   // → 食べています
            dog.Bark();  // → ワンワン!
        }
    }

    class Animal
    {
        public void Eat()
        {
            Console.WriteLine("食べています");
        }
    }

    class Dog : Animal
    {
        public void Bark()
        {
            Console.WriteLine("ワンワン!");
        }
    }
}

4. メリット

✅ コードの再利用

共通機能(例:Eat)は親クラスに1回書けばOK。

✅ 拡張性が高い

新しい動物(例:Cat)を追加しても、共通機能を再利用可能。

✅ 保守性の向上

共通の動作を親クラスだけ修正すれば、すべての子クラスに反映される。


5. overrideとvirtual

親クラスのメソッドを上書きしたい場合は、virtualとoverrideを使います。

InheritanceSampleOverrideプロジェクトとして作成しましょう

class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("何かを話します");
    }
}

class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("ワンワン!");
    }
}
var a = new Animal();
a.Speak();  // → 何かを話します

var d = new Dog();
d.Speak();  // → ワンワン!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritanceSampleOverride
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var a = new Animal();
            a.Speak();  // → 何かを話します

            var d = new Dog();
            d.Speak();  // → ワンワン!
        }
    }

    class Animal
    {
        public virtual void Speak()
        {
            Console.WriteLine("何かを話します");
        }
    }

    class Dog : Animal
    {
        public override void Speak()
        {
            Console.WriteLine("ワンワン!");
        }
    }
}

6. 実践例:Person → Student

InheritanceSamplePersonStudentプロジェクトとして作成しましょう

class Person
{
    public string Name { get; set; }

    public void Introduce()
    {
        Console.WriteLine($"私は{Name}です");
    }
}

class Student : Person
{
    public int Grade { get; set; }

    public void ShowGrade()
    {
        Console.WriteLine($"成績は{Grade}点です");
    }
}
var s = new Student { Name = "山田", Grade = 90 };
s.Introduce();   // → 私は山田です
s.ShowGrade();   // → 成績は90点です
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InheritanceSamplePersonStudent
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var s = new Student { Name = "山田", Grade = 90 };
            s.Introduce();   // → 私は山田です
            s.ShowGrade();   // → 成績は90点です
        }
    }

    class Person
    {
        public string Name { get; set; }

        public void Introduce()
        {
            Console.WriteLine($"私は{Name}です");
        }
    }

    class Student : Person
    {
        public int Grade { get; set; }

        public void ShowGrade()
        {
            Console.WriteLine($"成績は{Grade}点です");
        }
    }
}

7. 注意点:is-a関係の確認

継承を使うときは、「is-a」関係が成り立つかを確認しましょう。

  • ✅ Dog is-a Animal → OK
  • ❌ Car(車) is-a Animal → NG → 継承ではなくコンポジションを検討すべき

継承(Inheritance)を使うときに最も大切なのは、「is-a」関係(~は~である)が成り立つかどうかです。

この関係が不自然な場合、継承はかえって設計を複雑にし、意味が崩れてしまいます。


● is-a関係とは?

継承は「あるクラスが別のクラスの一種である」ときに使用すべきです。

✅ 正しい例:

Dog is-a Animal  // → OK(犬は動物の一種)
class Animal
{
    public void Breathe() => Console.WriteLine("呼吸している");
}

class Dog : Animal
{
    public void Bark() => Console.WriteLine("ワンワン!");
}

→ Dog は Animal を継承しても意味が通るし、設計上も自然です。


❌ 不適切な例:Car is-a Engine

Car is-a Engine  // → NG(車はエンジンの一種ではない)
  • 車(Car) は「エンジン(Engine)」の一種ではありません。
  • 「車はエンジンを含んでいる(has-an Engine)」という構成関係が正しいです。

▶ 正しい設計:コンポジション(has-a)

class Engine
{
    public void Start() => Console.WriteLine("エンジン始動");
}

class Car
{
    private Engine engine = new Engine();

    public void StartCar()
    {
        engine.Start();
        Console.WriteLine("車が走り出します");
    }
}
  • Car は Engine を持っている(has-a)
  • このようなときは継承ではなくコンポジションを使いましょう

✅ is-a と has-a の違いまとめ

関係用語対応設計
is-a(~である)継承Dog is-a AnimalDog : Animal
has-a(~を持つ)コンポジションCar has-an EngineCarがEngineをフィールドに持つ

🚫 よくある誤用に注意!

誤った継承設計なぜ問題か
class Car : Engine意味的に誤り。「車はエンジンの一種」ではない
class User : Loggerログ出力を継承すると、責務が混乱する場合がある。ILoggerなどのインターフェースで切り分けるべき

🧩 補足:Car is-a Vehicle は正しい

逆に、次のような継承は意味的にも設計的にも自然です。

Car is-a Vehicle  // ✅ OK(車は乗り物の一種)
abstract class Vehicle
{
    public abstract void Move();
}

class Car : Vehicle
{
    public override void Move()
    {
        Console.WriteLine("車が走行します");
    }
}

→ CarはVehicleの一種なので、継承は自然な設計です。


✅ まとめ(is-aを使う前のチェックリスト)

  • 「~は~の一種である」と言い換えられるか?
  • 継承元のメソッドが子クラスにとって意味を持つか?
  • 構成要素(has-a)を誤って継承していないか?

このような「is-a / has-a」の判断力は、オブジェクト指向設計における基礎体力です。継承の便利さに飛びつくのではなく、「意味のある関係か?」を常に問いながら設計していきましょう。


ご希望があれば、この記事全体を図解付きや、PDF出力用として再構成することも可能です。お気軽にお申し付けください。


8. 練習問題

問題:

次のようなVehicleクラスを継承して、Carクラスを作ってみましょう。

class Vehicle
{
    public void Run()
    {
        Console.WriteLine("走行中");
    }
}
  • Carクラスに「燃料を補給する」メソッドを追加してください
  • Carインスタンスで、両方のメソッドを呼び出してください

Vehicleクラスを継承したCarクラスに「燃料を補給する」機能(Refuel()メソッド)を追加し、両方のメソッドを呼び出します。


✅ サンプルコード

using System;

class Vehicle
{
    public void Run()
    {
        Console.WriteLine("走行中");
    }
}

class Car : Vehicle
{
    public void Refuel()
    {
        Console.WriteLine("燃料を補給しました");
    }
}

class Program
{
    static void Main()
    {
        Car myCar = new Car();
        myCar.Run();     // 親クラスのメソッド
        myCar.Refuel();  // 子クラスで追加したメソッド
    }
}

✅ 実行結果

走行中  
燃料を補給しました

✅ 解説

  • Car は Vehicle を継承しているので、Run() メソッドを使うことができます。
  • Car 独自のメソッド Refuel() を追加し、Main メソッドで両方のメソッドを呼び出しています。
  • 継承によって共通機能を使いつつ、派生クラス独自の機能を拡張できる例です。

9. まとめ

  • 継承はオブジェクト指向の基本中の基本
  • コードの再利用性と保守性を向上させる
  • 適切な場面で活用することで、アプリ全体の設計が美しくなる

実務では、継承よりコンポジション(委譲)の方が適切な場合も多いことも学んでいきましょう!


訪問数 30 回, 今日の訪問数 1回