C#ポリモーフィズムとは?初心者向けにサンプルコードでわかりやすく解説

はじめに

C#を学び始めると、「ポリモーフィズム」という言葉を目にする機会があります。名前だけを見ると難しそうですが、考え方はそれほど複雑ではありません。

ポリモーフィズムとは、簡単にいえば、同じメソッドを呼び出しても、対象となるオブジェクトによって異なる処理を実行できる仕組みです。

たとえば、「鳴く」という同じ操作でも、犬なら「ワンワン」、猫なら「ニャー」と鳴きます。利用する側は、それが犬か猫かを細かく判定せず、共通の「鳴く」という操作を呼び出すだけです。

この記事では、C#のポリモーフィズムについて、virtualoverrideの役割、基底クラスと派生クラスの関係を含めて、初心者向けにサンプルコードでわかりやすく解説します。

1. C#のポリモーフィズムとは

1-1. ポリモーフィズムの意味を初心者向けに解説

ポリモーフィズムは、日本語で「多態性」や「多相性」と訳されます。「1つのものが複数の形を持つ」という意味です。

C#におけるポリモーフィズムとは、共通の型や共通のメソッドを利用しながら、オブジェクトごとに異なる動作をさせることを指します。

たとえば、次のような動物がいるとします。

  • 犬は「ワンワン」と鳴く

  • 猫は「ニャー」と鳴く

  • 牛は「モー」と鳴く

それぞれの鳴き声は異なりますが、すべての動物に対して「鳴く」という共通の操作を行えます。

C#では、動物を表す共通の基底クラスを用意し、犬や猫などの派生クラスで処理を変更することで、この仕組みを実現できます。

1-2. 同じ操作で異なる処理を実行できる仕組み

ポリモーフィズムの重要な特徴は、同じメソッド名を使って異なる処理を実行できることです。

たとえば、次のようにSpeakメソッドを呼び出すコードがあるとします。

animal.Speak();

変数animalに犬のインスタンスが代入されていれば、犬の処理が実行されます。猫のインスタンスが代入されていれば、猫の処理が実行されます。

呼び出し側のコードは、どちらの場合でも同じです。

animal.Speak();

それでも、実際に実行される処理はオブジェクトの種類によって変わります。

このように、利用する側が個別のクラスを強く意識せず、共通の方法で複数のオブジェクトを扱えることが、ポリモーフィズムの基本です。

1-3. C#でポリモーフィズムを使うメリット

C#でポリモーフィズムを使う主なメリットは、コードを柔軟で変更しやすくできることです。

条件分岐を減らせる

ポリモーフィズムを使わない場合、オブジェクトの種類を調べるために、次のような条件分岐が増えることがあります。

if (animalType == "Dog"){Console.WriteLine("ワンワン");}else if (animalType == "Cat"){Console.WriteLine("ニャー");}else if (animalType == "Cow"){Console.WriteLine("モー");}

動物の種類が増えるたびに、条件分岐も追加しなければなりません。

ポリモーフィズムを利用すれば、それぞれのクラスに処理を任せられます。

animal.Speak();

呼び出し側では、オブジェクトの種類を判定する必要がありません。

クラスを追加しやすい

新しく鳥クラスを追加する場合でも、既存の呼び出し側のコードを大きく変更せずに対応できます。

鳥クラスで共通メソッドをオーバーライドすれば、既存の処理に組み込めます。

コードの役割を分けやすい

犬の処理は犬クラス、猫の処理は猫クラスに記述できます。各クラスが自分自身の動作を管理するため、コードの責任範囲が明確になります。

保守性が高まる

クラスごとに処理が整理されるため、機能の変更や不具合の修正がしやすくなります。大規模なアプリケーションほど、ポリモーフィズムによる保守性の向上が重要です。

1-4. 継承・カプセル化・抽象化との関係

ポリモーフィズムは、オブジェクト指向を構成する重要な考え方の一つです。継承、カプセル化、抽象化と組み合わせて利用されます。

継承との関係

継承とは、既存のクラスが持つ機能を、新しいクラスに引き継ぐ仕組みです。

たとえば、Animalクラスを継承して、DogクラスやCatクラスを作成できます。

class Dog : Animal{}

派生クラスは、基底クラスのメンバーを利用したり、処理を変更したりできます。継承は、C#でポリモーフィズムを実現する代表的な方法です。

カプセル化との関係

カプセル化とは、データと処理をクラスにまとめ、外部から必要以上に内部の状態を操作されないようにする考え方です。

利用する側は、クラス内部の詳しい実装を知らなくても、公開されたメソッドを通じて操作できます。ポリモーフィズムと組み合わせることで、呼び出し側を具体的な実装から切り離せます。

抽象化との関係

抽象化とは、複数のクラスに共通する特徴や操作を取り出すことです。

犬、猫、牛にはさまざまな違いがありますが、「名前を持つ」「鳴く」といった共通点があります。これらの共通点をAnimalクラスにまとめることが抽象化です。

抽象化した共通の型を利用し、具体的な動作を派生クラスに実装することで、ポリモーフィズムを活用しやすくなります。

2. C#でポリモーフィズムを実現する基本要素

2-1. 基底クラスと派生クラス

C#の継承では、継承元となるクラスを「基底クラス」、基底クラスを継承して作るクラスを「派生クラス」と呼びます。

次のコードでは、Animalが基底クラス、Dogが派生クラスです。

class Animal{public void Eat(){Console.WriteLine("食べ物を食べます。");}}

class Dog : Animal{}

DogクラスはAnimalクラスを継承しているため、AnimalクラスのEatメソッドを利用できます。

Dog dog = new Dog();dog.Eat();

実行結果は次のとおりです。

食べ物を食べます。

ただし、メソッドを継承するだけでは、オブジェクトごとに処理を変更できません。基底クラスのメソッドを派生クラスで変更するには、virtualoverrideを利用します。

2-2. virtualメソッドの役割

virtualは、基底クラスのメソッドに付けるキーワードです。

virtualを付けることで、「このメソッドは派生クラスで処理を上書きしてもよい」と示せます。

class Animal{public virtual void Speak(){Console.WriteLine("動物が鳴きます。");}}

この例では、Speakメソッドが仮想メソッドとして定義されています。

virtualメソッドには、基底クラスで標準となる処理を記述できます。派生クラスで処理を変更しなかった場合は、基底クラスに記述された処理が実行されます。

なお、virtualはメソッド以外に、プロパティやインデクサーなどにも使用できます。

2-3. overrideによるメソッドのオーバーライド

overrideは、基底クラスのvirtualメソッドを派生クラスで上書きするときに使用します。

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

AnimalクラスのSpeakメソッドは「動物が鳴きます」と表示しますが、Dogクラスでは「ワンワン」と表示する処理に変更されています。

同じように、猫クラスも定義できます。

class Cat : Animal{public override void Speak(){Console.WriteLine("ニャー");}}

overrideできるのは、基底クラスでvirtualabstract、またはoverrideとして定義されたメソッドです。

基底クラス側にvirtualが付いていない通常のメソッドを、overrideで上書きすることはできません。

2-4. 基底クラス型の変数に派生クラスのインスタンスを代入する

派生クラスのインスタンスは、基底クラス型の変数に代入できます。

Animal animal = new Dog();

変数の型はAnimalですが、実際に作成されているオブジェクトはDogです。

この代入が可能なのは、DogAnimalを継承しており、犬も動物の一種として扱えるためです。

同じ変数に、別の派生クラスのインスタンスを代入することもできます。

Animal animal1 = new Dog();Animal animal2 = new Cat();

どちらもAnimal型として扱えますが、内部に保持している実際のオブジェクトは異なります。

このように、複数の派生クラスを共通の基底クラス型で扱うことが、ポリモーフィズムを活用するための重要なポイントです。

2-5. 実行時に呼び出すメソッドが決まる仕組み

基底クラス型の変数からvirtualメソッドを呼び出すと、変数の型ではなく、実際に代入されているオブジェクトの型に応じて実行するメソッドが決まります。

Animal animal = new Dog();animal.Speak();

変数animalの型はAnimalですが、実際のオブジェクトはDogです。そのため、DogクラスでオーバーライドされたSpeakメソッドが実行されます。

実行結果は次のとおりです。

ワンワン

次のように猫のインスタンスを代入した場合は、猫の処理が実行されます。

animal = new Cat();animal.Speak();

実行結果は次のとおりです。

ニャー

このように、実際に呼び出されるメソッドがプログラムの実行時に決まる仕組みを「動的ディスパッチ」と呼びます。

3. ポリモーフィズムの基本をサンプルコードで解説

ここからは、動物を題材にしたサンプルコードを使い、C#のポリモーフィズムを実際に確認します。

3-1. 基底クラスを定義する

最初に、すべての動物に共通する基底クラスを定義します。

public class Animal{public string Name { get; }

public Animal(string name){Name = name;}public virtual void Speak(){Console.WriteLine($"{Name}が鳴きました。");}

}

Animalクラスには、次のメンバーを定義しています。

  • 動物の名前を保持するNameプロパティ

  • 名前を受け取るコンストラクター

  • 鳴き声を出力するSpeakメソッド

Speakメソッドにはvirtualを付けています。そのため、Animalクラスを継承するクラスは、必要に応じて処理をオーバーライドできます。

3-2. 派生クラスでメソッドをオーバーライドする

次に、Animalクラスを継承したDogクラスとCatクラスを定義します。

public class Dog : Animal{public Dog(string name) : base(name){}

public override void Speak(){Console.WriteLine($"{Name}:ワンワン");}

}

public class Cat : Animal{public Cat(string name) : base(name){}

public override void Speak(){Console.WriteLine($"{Name}:ニャー");}

}

DogクラスとCatクラスは、それぞれSpeakメソッドをオーバーライドしています。

DogクラスのSpeakメソッドを呼び出すと「ワンワン」、Catクラスでは「ニャー」と表示されます。

コンストラクターで使用しているbase(name)は、基底クラスであるAnimalのコンストラクターを呼び出すための記述です。

public Dog(string name) : base(name){}

このコードによって、犬の名前が基底クラスのNameプロパティに設定されます。

3-3. 基底クラス型から派生クラスの処理を呼び出す

作成したクラスを使い、ポリモーフィズムを確認します。

public class Program{public static void Main(){Animal dog = new Dog("ポチ");Animal cat = new Cat("タマ");

    dog.Speak();cat.Speak();}

}

実行結果は次のとおりです。

ポチ:ワンワンタマ:ニャー

dogcatは、どちらもAnimal型の変数です。しかし、実際に代入されているオブジェクトが異なるため、それぞれ別のSpeakメソッドが実行されています。

さらに、基底クラス型の配列やリストを使うと、異なる派生クラスのオブジェクトをまとめて扱えます。

public class Program{public static void Main(){List<Animal> animals = new List<Animal>{new Dog("ポチ"),new Cat("タマ"),new Dog("コタロウ")};

    foreach (Animal animal in animals){animal.Speak();}}

}

実行結果は次のとおりです。

ポチ:ワンワンタマ:ニャーコタロウ:ワンワン

foreach文の中では、オブジェクトが犬なのか猫なのかを判定していません。

呼び出し側は、すべてのオブジェクトに対して共通のSpeakメソッドを呼び出しているだけです。それぞれの具体的な処理は、各派生クラスに任せています。

これが、C#におけるポリモーフィズムの基本的な使い方です。

サンプルコード全体は次のとおりです。

using System;using System.Collections.Generic;

public class Animal{public string Name { get; }

public Animal(string name){Name = name;}public virtual void Speak(){Console.WriteLine($"{Name}が鳴きました。");}

}

public class Dog : Animal{public Dog(string name) : base(name){}

public override void Speak(){Console.WriteLine($"{Name}:ワンワン");}

}

public class Cat : Animal{public Cat(string name) : base(name){}

public override void Speak(){Console.WriteLine($"{Name}:ニャー");}

}

public class Program{public static void Main(){List<Animal> animals = new List<Animal>{new Dog("ポチ"),new Cat("タマ"),new Dog("コタロウ")};

    foreach (Animal animal in animals){animal.Speak();}}

}

まとめ

C#のポリモーフィズムとは、共通の型や操作を使いながら、実際のオブジェクトに応じて異なる処理を実行する仕組みです。

基底クラスでメソッドにvirtualを付け、派生クラスでoverrideすることで、メソッドの処理をクラスごとに変更できます。

また、派生クラスのインスタンスを基底クラス型の変数に代入すると、複数の異なるオブジェクトを共通の方法で扱えます。呼び出すメソッドは、変数の型ではなく、実際に代入されているオブジェクトの型によって決まります。

ポリモーフィズムを活用すると、オブジェクトの種類を判定する条件分岐を減らし、新しいクラスを追加しやすい設計にできます。コードの柔軟性や保守性を高めるためにも、まずは基底クラス、派生クラス、virtualoverrideの関係から理解していきましょう。