CodeGym /コース /C# SELF /C#のバーチャルメソッド:ポリモーフィズムとオーバーライド

C#のバーチャルメソッド:ポリモーフィズムとオーバーライド

C# SELF
レベル 20 , レッスン 2
使用可能

1. はじめに

例えば、Animalっていうベースクラスがあって、Speak()メソッドがあるとする。デフォルトでは全ての動物が"何かの音"って出力する。でも、DogCatみたいなサブクラスを作って、それぞれ"何かの音"じゃなくて本当の鳴き声("ワン!""ニャー!")を言わせたいよね。

もちろん、各サブクラスで独自のSpeak()メソッドを書いてもいいけど、もしAnimal[]みたいなコレクションで扱ってたら、animal.Speak()を呼んでも結局Animalのメソッドが呼ばれちゃう。C#はデフォルトで変数の型を見て判断するから、実際のオブジェクトの型じゃないんだよね。

例え話で説明すると:


Animal dog = new Dog();
dog.Speak(); // さて、何が出力される?

デフォルトだと"何かの音"で、"ワン!"じゃない。たとえDogに独自のSpeak()があってもね。どうすればいい?ベースクラスでvirtual、サブクラスでoverrideを付けて宣言する必要があるんだ。

バーチャルメソッドって何?

virtualメソッドは、ベースクラスで virtual修飾子を付けて宣言されてて、サブクラスで overrideで上書きできるようにするためのメソッドだよ。

こういうメソッドをベースクラスの参照で呼ぶと、実際の「中身」のオブジェクトのメソッドが呼ばれる。現実世界みたいに、動物を飼いならしてて、それが猫なら"ニャー!"って鳴くし、"何かの音"じゃないよね。

バーチャルメソッドはC#のポリモーフィズムの基本だよ。

2. バーチャルメソッドの宣言

バーチャルメソッドの宣言方法

ベースクラスでvirtualキーワードを付けてメソッドを宣言する:


public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("何かの音");
    }
}
ベースクラスでバーチャルメソッドを宣言

サブクラスではoverrideキーワードを使う:


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

public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("ニャー!");
    }
}
サブクラスでバーチャルメソッドをオーバーライド

例:ポリモーフィズムのデモ


// 例えば、Program.csファイルで

// ベースクラスAnimal、バーチャルメソッドSpeakあり
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("何かの音");
    }
}

// Dogクラス、Speakの動作をオーバーライド
public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("ワン!");
    }
}

// CatクラスもSpeakをオーバーライド
public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("ニャー!");
    }
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };

        foreach (Animal animal in animals)
        {
            animal.Speak(); // 出力:"何かの音", "ワン!", "ニャー!"
        }
    }
}

ね、バーチャルメソッドって魔法みたいでしょ?変数の型がAnimalでも、実際の実装のメソッドが呼ばれるんだ。

3. 「裏側」では何が起きてる?バーチャルテーブル

仕組みをざっくり説明

メソッドをバーチャルに宣言すると、コンパイラが各型ごとに特別な「バーチャルテーブル」にそのメソッドを追加する(まるで料理ごとに食堂のメニューがあるみたいな感じ)。こういうメソッドを呼ぶとき、C#は変数の型じゃなくて、実際に参照してる中身を見て、その型に合ったメソッドを「差し替えて」くれるんだ。

つまり、クラスに独自のレシピがあれば、C#が自動でそのレシピを使ってくれるってこと。

例え話:細かい条件を「追記できる契約書」

例えば、賃貸契約書(ベースクラス)があって、デフォルトで「毎月定額を払う」(PayRent()メソッド)って書いてある。でも、ちょっとズルい大家さんなら、契約書の付録に特別条件を書き加えることを許す(バーチャルメソッド)。借主(サブクラス)がこの権利を使えば、overrideで自分用の支払い方法を書ける。そうすると、支払いが特別ルールになるってわけ。

4. バーチャルメソッドを使うべきときは?

  • ベースクラスのメソッドがほとんどのサブクラスで共通だけど、一部だけ独自の動作が必要な場合。
  • クラス階層を作って、ロジックの一部を拡張・変更できるようにしたいとき。
  • ビジネスロジックみたいに、いろんな操作タイプごとに個別の動作が必要な柔軟な設計をしたいとき。

実際のプロジェクトでも、バーチャルメソッドはテンプレートメソッド(Template Method)やストラテジー(Strategy)パターンの基本だし、OOPの半分くらいはこれでできてる。

普通のメソッドとの比較

普通のメソッド バーチャルメソッド
オーバーライドできる? いいえ はい(override
呼び出し方 変数の型で決まる 「本当の」オブジェクトの型で決まる
ポリモーフィズム ない ある

よくある質問

  • コンストラクタをバーチャルにできる?
    できない!コンストラクタは絶対にバーチャルにならないし、常にオブジェクトの型で動く。
  • バーチャルメソッドはstaticにできる?
    無理、インスタンスメソッドだけがバーチャルにできる。staticメソッドはポリモーフィズムに関係ないし、オブジェクトがなければ誰がオーバーライドするの?
  • フィールドはバーチャルにできる?
    できない、メソッド・プロパティ・イベントだけ。

5. 実践:「動物の世界」アプリを進化させよう

この学習アプリで、もう動物の簡単な階層は作ったよね。じゃあ、動物ごとに違う食べ方ができるように、新しいバーチャルメソッドを追加しよう!

コメント付きコード例


// ベースクラスでバーチャルメソッドEatを定義
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("何かの音");
    }

    public virtual void Eat()
    {
        Console.WriteLine("何でも食べる。");
    }
}

// DogでEatをオーバーライド
public class Dog : Animal
{
    public override void Speak() => Console.WriteLine("ワン!");
    public override void Eat() => Console.WriteLine("ドッグフードを食べる。");
}

// Catも独自の動作
public class Cat : Animal
{
    public override void Speak() => Console.WriteLine("ニャー!");
    public override void Eat() => Console.WriteLine("魚を食べる。");
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };
        foreach (var animal in animals)
        {
            animal.Speak();
            animal.Eat();
            Console.WriteLine("---");
        }
    }
}

これで、猫も犬もそれぞれの食べ方ができるようになった!ベースのAnimalは相変わらず謎のものを食べてるけど、それが「デフォルト」ってやつだね。

6. よくあるミスや注意点

  • サブクラスでoverrideを付け忘れると、ベースクラスのメソッドのままになっちゃう。
  • 逆に、ベースクラスでvirtualを付け忘れると、サブクラスでoverrideが書けない。

virtualなしでoverrideは書けない

C#のコンパイラは、ベースクラスでバーチャル(またはabstract)に宣言されてないメソッドはオーバーライドさせてくれないよ。

overrideの意味

overrideは、コンパイラにもコードを読む人にも「これは新しいメソッドじゃなくて、ベースクラスのやつをちゃんと上書きしてるよ」って明示するため。うっかりシグネチャが同じだけの新しいメソッドを書いちゃうミスを防げるんだ。

overrideを忘れて新しいメソッドを書いた場合

public class Dog : Animal
{
    public void Speak()
    {
        Console.WriteLine("ワン!");
    }
}
これは オーバーライドじゃない!ポリモーフィズムは効かないよ。

でも、これはオーバーライドじゃないAnimal型の変数でDogを扱うと、呼ばれるのは動物のメソッド("何かの音")で、犬のじゃない。型の思わぬ落とし穴だね!

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION