ポリモーフィズム (C# プログラミング ガイド)
ポリモーフィズムは、カプセル化と継承に次ぐ、オブジェクト指向プログラミングの第 3 の柱と言われることがよくあります。ポリモーフィズムは、ギリシャ語で "多形" を意味し、次の 2 つの側面を持っています。
メソッド パラメーター、配列、コレクションなどに渡された派生クラスのオブジェクトは、実行時に基本クラスのオブジェクトとして扱われることがあります。この場合、オブジェクトの宣言された型は、その実行時の型と同じではなくなります。
基本クラスでは、仮想メソッドを定義して実装できます。派生クラスでそれをオーバーライドすると、独自の定義と実装を提供できます。実行時には、クライアント コードがメソッドを呼び出したとき、CLR によってオブジェクトの実行時の型が検索され、仮想メソッドのオーバーライドが呼び出されます。このように、ソース コード内で基本クラスのメソッドを呼び出したら、派生クラスのメソッドが実行されるようにできます。
仮想メソッドを使用すると、関連するオブジェクトのグループを一定の方法で扱うことができます。たとえば、描画サーフェイスにさまざまな種類の図形を作成できる描画アプリケーションがあるとします。コンパイル時には、ユーザーがどのような種類の図形を作成するかわかりません。しかし、アプリケーションでは、作成されたさまざまな種類の図形を追跡し、ユーザーのマウス操作に応じて更新する必要があります。ポリモーフィズムを使用すると、2 つの基本的な手順でこの問題を解決できます。
各図形クラスが共通の基本クラスから派生するようなクラス階層を作成します。
仮想メソッドを使用して、基本クラスの 1 つのメソッドを呼び出すことで、派生クラスの適切なメソッドが呼び出されるようにします。
まず、Shape という基本クラスと、Rectangle、Circle、Triangle などの派生クラスを作成します。Shape クラスで Draw という仮想メソッドを定義し、各派生クラスでそれをオーバーライドして、そのクラスが表す特定の図形を描画します。List<Shape> オブジェクトを作成し、Circle、Triangle、および Rectangle を追加します。描画サーフェイスを更新するには、foreach ループを使用してリストを反復処理し、リスト内の各 Shape オブジェクトの Draw メソッドを呼び出します。リスト内の各オブジェクトの宣言された型は Shape ですが、呼び出されるのは実行時の型 (それぞれの派生クラスでオーバーライドされたメソッド) になります。
public class Shape
{
// A few example members
public int X { get; private set; }
public int Y { get; private set; }
public int Height { get; set; }
public int Width { get; set; }
// Virtual method
public virtual void Draw()
{
Console.WriteLine("Performing base class drawing tasks");
}
}
class Circle : Shape
{
public override void Draw()
{
// Code to draw a circle...
Console.WriteLine("Drawing a circle");
base.Draw();
}
}
class Rectangle : Shape
{
public override void Draw()
{
// Code to draw a rectangle...
Console.WriteLine("Drawing a rectangle");
base.Draw();
}
}
class Triangle : Shape
{
public override void Draw()
{
// Code to draw a triangle...
Console.WriteLine("Drawing a triangle");
base.Draw();
}
}
class Program
{
static void Main(string[] args)
{
// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used whereever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();
shapes.Add(new Rectangle());
shapes.Add(new Triangle());
shapes.Add(new Circle());
// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (Shape s in shapes)
{
s.Draw();
}
// Keep the console open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Drawing a rectangle
Performing base class drawing tasks
Drawing a triangle
Performing base class drawing tasks
Drawing a circle
Performing base class drawing tasks
*/
C# では、すべての型がポリモーフィックです。これは、ユーザー定義型を含むすべての型が Object から派生するためです。
ポリモーフィズムの概要
仮想メンバー
基本クラスから派生クラスを継承すると、派生クラスは、基本クラスのすべてのメソッド、フィールド、プロパティ、およびイベントを継承します。派生クラスの設計者は、次の点を選択できます。
基本クラスの仮想メンバーをオーバーライドするかどうか
最も近い基本クラスのメンバーを、オーバーライドせずに継承するかどうか
これらのメンバーの仮想でない実装を新しく定義して、基本クラスの実装を隠ぺいするかどうか
派生クラスが基本クラスのメンバーをオーバーライドできるのは、基本クラスのメンバーが virtual または abstract として宣言されている場合だけです。派生メンバーでは、override キーワードを使用して、そのメソッドが仮想呼び出しに加わることを明示的に示す必要があります。次に例を示します。
public class BaseClass
{
public virtual void DoWork() { }
public virtual int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public override void DoWork() { }
public override int WorkProperty
{
get { return 0; }
}
}
フィールドは仮想メンバーにできません。仮想メンバーにできるのは、メソッド、プロパティ、イベント、およびインデクサーに限られます。派生クラスが仮想メンバーをオーバーライドすると、派生クラスのメンバーは、そのクラスのインスタンスが基本クラスのインスタンスとしてアクセスされるときでも呼び出されます。次に例を示します。
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Also calls the new method.
仮想メソッドとプロパティを使用すると、派生クラスは、基本クラスのメソッドの実装を使用せずに基本クラスを拡張できます。詳細については、「Override キーワードと New キーワードによるバージョン管理 (C# プログラミング ガイド)」を参照してください。1 つまたは一連のメソッドを定義し、その実装を派生クラスに任せるもう 1 つの方法として、インターフェイスがあります。詳細については、「インターフェイス (C# プログラミング ガイド)」を参照してください。
新しいメンバーによる基本クラスのメンバーの隠ぺい
派生メンバーに基本クラスのメンバーと同じ名前を付けながら、そのメンバーが仮想呼び出しに加わらないようにするには、new キーワードを使用します。new キーワードは、置き換えられるクラス メンバーの戻り値の型の前に配置します。次に例を示します。
public class BaseClass
{
public void DoWork() { WorkField++; }
public int WorkField;
public int WorkProperty
{
get { return 0; }
}
}
public class DerivedClass : BaseClass
{
public new void DoWork() { WorkField++; }
public new int WorkField;
public new int WorkProperty
{
get { return 0; }
}
}
基本クラスのメンバーが隠ぺいされても、派生クラスのインスタンスを基本クラスのインスタンスにキャストすることで、クライアント コードから基本クラスのメンバーにアクセスできます。次に例を示します。
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
派生クラスによる仮想メンバーのオーバーライドの禁止
仮想メンバーは、それを最初に宣言したクラスとの間でどれほど多くのクラスが宣言されても、いつまでも仮想のままです。たとえば、クラス A が仮想メンバーを宣言し、クラス B がクラス A から派生し、クラス C が クラス B から派生した場合、クラス C は仮想メンバーを継承し、クラス B がその仮想メンバーのオーバーライドを宣言した場合でも、そのメンバーをオーバーライドできます。次に例を示します。
public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}
派生クラスでは、オーバーライドを sealed として宣言することで仮想継承を中止できます。この場合、クラス メンバーの宣言で、override キーワードの前に sealed キーワードを指定する必要があります。次に例を示します。
public class C : B
{
public sealed override void DoWork() { }
}
上の例では、DoWork メソッドは C から派生したどのクラスに対しても仮想メソッドではありません。C のインスタンスに対しては、B 型や A 型にキャストされた場合でも、依然として仮想メソッドです。シール メソッドは、次のコード例に示すように、new キーワードを使用して派生クラスに置き換えることができます。
public class D : C
{
public new void DoWork() { }
}
このコード例では、DoWork が、D 型の変数を使用して D で呼び出されると、新しい DoWork が呼び出されます。また、C 型、B 型、または A 型の変数を使用して D のインスタンスにアクセスした場合、DoWork への呼び出しは、仮想継承の規則に従って、クラス C の DoWork の実装に転送されます。
派生クラスからの基本クラスの仮想メンバーへのアクセス
メソッドやプロパティを置き換えたり、オーバーライドしたりした派生クラスでは、base キーワードを使用することで、基本クラスのメソッドやプロパティにアクセスできます。次に例を示します。
public class Base
{
public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
public override void DoWork()
{
//Perform Derived's work here
//...
// Call DoWork on base class
base.DoWork();
}
}
詳細については、「base」を参照してください。
[!メモ]
仮想メンバーの場合、その固有の実装で base を使用して、その仮想メンバーの基本クラス実装を呼び出すことをお勧めします。基本クラスの動作を実現することで、派生クラスは、その固有の動作を実装することに集中できます。基本クラス実装を呼び出さない場合は、基本クラスの動作と互換性のある動作を派生クラスで実現する必要があります。
このセクションの内容
参照
関連項目
抽象クラスとシール クラス、およびクラス メンバー (C# プログラミング ガイド)