コンパイル時 vs 実行時ポリモーフィズム
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 25/70。
ポリモーフィズム(多態性)とは「多くの形態」を意味し、同じコードが文脈に応じて異なる動作をする能力のことです。C#は2つの異なる種類のポリモーフィズムをサポートしており、それぞれがいつ解決されるかを理解することは、より柔軟なコードを書くのに役立ちます。
コンパイル時ポリモーフィズム(静的ポリモーフィズムとも呼ばれます)は、プログラムが実行される前にコンパイラによって解決されます。メソッドのオーバーロードがその主な例です。複数のメソッドが同じ名前を共有しますが、異なるパラメータを持っています:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // Compiler picks int version
calc.Add(5.0, 3.0); // Compiler picks double versionコンパイラは引数の型を検証し、コンパイル時に適切なメソッドを選択します。この決定は、プログラムが実行される前に確定します。
実行時ポリモーフィズム (動的ポリモーフィズム) は、プログラムの実行中に解決されます。これは virtual メソッドと override メソッドで発生します。オブジェクトの実際の型によって、どのメソッドが実行されるかが決まります。
Animal pet = new Dog(); // Animalとして宣言されているが、実際にはDog
pet.Speak(); // 実行時にDogのSpeak()を呼び出すコンパイラは、どの Speak() が実行されるかを知りません。その決定は、オブジェクトの実際の型に基づいて実行時に行われます。これこそが、柔軟で拡張可能なシステムを構築する上で、継承ベースのポリモーフィズムを非常に強力なものにしている理由です。
チャレンジ
簡単2つのタイプのポリモーフィズムを実際に動作させるコンバーターシステムを構築しましょう。メソッドのオーバーロード(コンパイル時ポリモーフィズム)を使用するクラスと、仮想/オーバーライドメソッド(実行時ポリモーフィズム)を使用する継承階層を作成します。
コードは4つのファイルに分けて構成します:
Converter.cs:Conversion名前空間内にConverterクラスを定義します。このクラスは、メソッドのオーバーロードを通じてコンパイル時ポリモーフィズムを示します。3つのオーバーロードされたConvertメソッドを作成してください:Convert(int value)は"Integer: {value}"を返しますConvert(double value)は"Double: {value}"を返しますConvert(string value)は"String: {value}"を返します
Shape.cs:Conversion名前空間内にベースとなるShapeクラスを定義します。このクラスにはNameプロパティ(string)と、それを設定するコンストラクタがあります。"This is a {Name}"を返すvirtualメソッドDescribe()を含めてください。Circle.cs:Conversion名前空間内にShapeを継承するCircleクラスを定義します。Radiusプロパティ(double)を追加します。コンストラクタは半径を受け取り、ベースコンストラクタに"Circle"を渡します。Describe()をオーバーライドして、"This is a Circle with radius {Radius}"を返すようにします。Program.cs: メインファイルで、両方のタイプのポリモーフィズムを実演します。まず、Converterを作成し、入力値を使用して3つのオーバーロードされたメソッドすべてを呼び出します。次にCircleを作成してShape型の変数に格納し、Describe()を呼び出して実行時ポリモーフィズムを示します。
4つの入力を受け取ります:
- 整数値
- double値
- 文字列値
- 円の半径(double)
出力を以下の形式で表示してください:
Compile-time Polymorphism:
{Convert(int) result}
{Convert(double) result}
{Convert(string) result}
Runtime Polymorphism:
{Describe() result from Shape variable holding Circle}例えば、入力が 42、3.14、Hello、5.5 の場合、出力は以下のようになります:
Compile-time Polymorphism:
Integer: 42
Double: 3.14
String: Hello
Runtime Polymorphism:
This is a Circle with radius 5.5重要な違いに注目してください。コンパイラはコンパイル時に引数の型に基づいてどの Convert メソッドを呼び出すかを決定しますが、Describe() メソッドは、たとえ Shape 型の変数に格納されていても、実行時に実際のオブジェクトの型に基づいて解決されます!
チートシート
ポリモーフィズム(多態性)とは「多くの形態」を意味し、同じコードがコンテキストに応じて異なる動作をする能力のことです。
コンパイル時ポリモーフィズム(静的ポリモーフィズム)は、プログラムが実行される前にコンパイラによって解決されます。メソッドのオーバーロードがその主な例です:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // Compiler picks int version
calc.Add(5.0, 3.0); // Compiler picks double versionコンパイラは引数の型を調べ、コンパイル時に正しいメソッドを選択します。
実行時ポリモーフィズム(動的ポリモーフィズム)は、プログラムの実行中に virtual および override メソッドを使用して解決されます:
Animal pet = new Dog(); // Declared as Animal, actually a Dog
pet.Speak(); // Calls Dog's Speak() at runtime宣言された変数の型ではなく、オブジェクトの実際の型によって、実行時にどのメソッドが実行されるかが決定されます。
自分で試してみよう
using System;
using Conversion;
class Program
{
public static void Main(string[] args)
{
// 入力を読み込む
int intValue = Convert.ToInt32(Console.ReadLine());
double doubleValue = Convert.ToDouble(Console.ReadLine());
string stringValue = Console.ReadLine();
double radius = Convert.ToDouble(Console.ReadLine());
// TODO: コンパイル時ポリモーフィズムを実演する
// Converter オブジェクトを作成し、オーバーロードされた 3 つの Convert メソッドをすべて呼び出す
Console.WriteLine("Compile-time Polymorphism:");
// intValue、doubleValue、および stringValue を使用して Convert を呼び出す
// TODO: 実行時ポリモーフィズムを実演する
// Circle オブジェクトを作成し、それを Shape 変数に格納する
// 次に、Shape 変数に対して Describe() を呼び出す
Console.WriteLine("Runtime Polymorphism:");
// Circle を作成し、Describe() を呼び出す
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
5ポリモーフィズムとインターフェース
コンパイル時 vs 実行時ポリモーフィズムインターフェース vs 抽象クラス複数のインターフェース明示的なインターフェースアップキャストとダウンキャストまとめ - 図形計算機