継承と派生クラス (C# と Java の比較)
更新 : 2007 年 11 月
既存のクラスの機能は、既存のクラスから派生する新しいクラスを作成することによって拡張できます。派生クラスは基本クラスのプロパティを継承します。必要に応じて、メソッドやプロパティを追加したり、オーバーライドしたりできます。
C# では、継承とインターフェイス実装が共に : 演算子によって定義されます。この演算子は、Java の extends および implements に相当します。クラス宣言では、必ず基本クラスを左端に配置する必要があります。
Java と同様に C# も多重継承をサポートしないため、複数のクラスからクラスを継承できません。ただし、Java と同様に多重継承用のインターフェイスを使用することはできます。
次のコードは、点の位置を表す x と y の 2 つのプライベート メンバ変数を含む CoOrds というクラスを定義しています。これらの変数には、それぞれ X、Y というプロパティを介してアクセスします。
public class CoOrds
{
private int x, y;
public CoOrds() // constructor
{
x = 0;
y = 0;
}
public int X
{
get { return x; }
set { x = value; }
}
public int Y
{
get { return y; }
set { y = value; }
}
}
ColorCoOrds という新しいクラスを CoOrds クラスから次のように派生させます。
public class ColorCoOrds : CoOrds
これで、ColorCoOrds は基本クラスのすべてのフィールドとメソッドを継承します。派生クラスには、必要に応じて新しいフィールドやメソッドを追加して機能を拡張できます。次の例では、クラスに色を付けるためにプライベート メンバとアクセサを追加します。
public class ColorCoOrds : CoOrds
{
private System.Drawing.Color screenColor;
public ColorCoOrds() // constructor
{
screenColor = System.Drawing.Color.Red;
}
public System.Drawing.Color ScreenColor
{
get { return screenColor; }
set { screenColor = value; }
}
}
派生クラスのコンストラクタは、基本クラス (Java の用語ではスーパークラス) のコンストラクタを暗黙的に呼び出します。継承時には、基本クラスのコンストラクタがすべて呼び出された後に、クラス階層で表示される順番で派生クラスのコンストラクタが呼び出されます。
基本クラスへの型キャスト
Java と同様に、C# では、派生型のオブジェクトへの有効な参照が基本クラス参照に含まれている場合でも、基本クラスへの参照を使用して派生クラスのメンバやメソッドにアクセスできません。
派生クラスは、次のように派生型への参照を暗黙的に使用して参照できます。
ColorCoOrds color1 = new ColorCoOrds();
CoOrds coords1 = color1;
このコードの基本クラス参照 coords1 には、color1 参照のコピーが含まれます。
base キーワード
サブクラス内の基本クラス メンバには、これらのメンバがスーパークラスでオーバーライドされている場合でも、base キーワードを使用してアクセスできます。たとえば、基本クラスと同じシグネチャを持つメソッドを含む派生クラスを作成することもできます。このメソッドの前に new キーワードを配置すると、このメソッドは、派生クラスに属するまったく新しいメソッドになります。その場合でも、base キーワードを使用すると、基本クラス内の元のメソッドにアクセスするためのメソッドを提供できます。
たとえば、x 座標と y 座標を入れ替える Invert() というメソッドが CoOrds 基本クラスにあるとします。この場合、次のようなコードを使用して、このメソッドの代替要素を ColorCoOrds 派生クラスに提供できます。
public new void Invert()
{
int temp = X;
X = Y;
Y = temp;
screenColor = System.Drawing.Color.Gray;
}
このメソッドは x と y を入れ替えてから、座標の色を灰色に設定します。このメソッドの基本実装にアクセスできるようにする場合は、次のような新しいメソッドを ColorCoOrds で作成します。
public void BaseInvert()
{
base.Invert();
}
これで、BaseInvert() メソッドを呼び出すと、ColorCoOrds オブジェクトの基本メソッドが呼び出されます。
ColorCoOrds color1 = new ColorCoOrds();
color1.BaseInvert();
この場合、次のように ColorCoOrds のインスタンスに基本クラスへの参照を割り当て、このオブジェクトのメソッドを呼び出しても同じ結果が得られます。
CoOrds coords1 = color1;
coords1.Invert();
コンストラクタの選択
基本クラス オブジェクトは、常に派生クラスより前に作成されます。そのため、基本クラスのコンストラクタは、派生クラスのコンストラクタより前に実行されます。基本クラスに複数のコンストラクタがある場合、派生クラスは、呼び出すコンストラクタを選択できます。たとえば、CoOrds クラスを次のように変更すると、2 番目のコンストラクタを追加できます。
public class CoOrds
{
private int x, y;
public CoOrds()
{
x = 0;
y = 0;
}
public CoOrds(int x, int y)
{
this.x = x;
this.y = y;
}
}
続いて、base キーワードを使用して ColorCoOrds クラスを次のように変更すると、使用できるコンストラクタのうちの特定のものを使用できます。
public class ColorCoOrds : CoOrds
{
public System.Drawing.Color color;
public ColorCoOrds() : base ()
{
color = System.Drawing.Color.Red;
}
public ColorCoOrds(int x, int y) : base (x, y)
{
color = System.Drawing.Color.Red;
}
}
Java の場合、この機能は super キーワードを使用して実装します。
メソッドのオーバーライド
派生クラスでは、宣言したメソッドに新しい実装を提供することで、基本クラスのメソッドをオーバーライドできます。Java と C# の間には、Java のメソッドは既定で仮想メソッドとしてマークされるのに対し、C# のメソッドは virtual 修飾子を使って仮想メソッドとして明示的にマークする必要があるという重要な違いがあります。メソッドに加えてプロパティ アクセサも同じようにオーバーライドできます。
仮想メソッド
派生クラスでオーバーライドされるメソッドは、virtual 修飾子を使用して宣言します。派生クラスでは、オーバーライドされたメソッドは、override 修飾子を使用して宣言します。
override 修飾子は、基本クラスで同じ名前とシグネチャを持つメソッドまたプロパティに置き換わる、派生クラスのメソッドまたはプロパティを示します。オーバーライドされる基本メソッドは、virtual、abstract、または override として宣言する必要があります。非仮想メソッドや静的メソッドをこのようにオーバーライドすることはできません。メソッドまたはプロパティは、オーバーライドする側もオーバーライドされる側も同じアクセス レベルの修飾子を持つ必要があります。
派生クラスでオーバーライドされる、override 修飾子を持つ StepUp という仮想メソッドの例を次に示します。
public class CountClass
{
public int count;
public CountClass(int startValue) // constructor
{
count = startValue;
}
public virtual int StepUp()
{
return ++count;
}
}
class Count100Class : CountClass
{
public Count100Class(int x) : base(x) // constructor
{
}
public override int StepUp()
{
return ((base.count) + 100);
}
}
class TestCounters
{
static void Main()
{
CountClass counter1 = new CountClass(1);
CountClass counter100 = new Count100Class(1);
System.Console.WriteLine("Count in base class = {0}", counter1.StepUp());
System.Console.WriteLine("Count in derived class = {0}", counter100.StepUp());
}
}
このコードを実行すると、派生クラスのコンストラクタは、基本クラスのメソッド本体を使用します。そのため、このコードを複製せずに count メンバを初期化できます。出力は次のとおりです。
Count in base class = 2
Count in derived class = 101
抽象クラス
抽象クラスでは、1 つ以上のメソッドやプロパティを抽象項目として宣言します。このようなメソッドには、それらを宣言したクラスで実装が提供されませんが、抽象クラスには、非抽象メソッド (実装が提供されているメソッド) を含めることもできます。抽象クラスは直接初期化できず、派生クラスとしてのみ初期化できます。このような派生クラスでは、派生メンバ自体が抽象項目として宣言されている場合を除き、override キーワードを使用して、すべての抽象メソッドおよび抽象プロパティに実装を提供する必要があります。
Employee 抽象クラスを宣言する例を次に示します。また、Employee クラスで定義された Show() 抽象メソッドの実装を提供する Manager という派生クラスも作成します。
public abstract class Employee
{
protected string name;
public Employee(string name) // constructor
{
this.name = name;
}
public abstract void Show(); // abstract show method
}
public class Manager: Employee
{
public Manager(string name) : base(name) {} // constructor
public override void Show() //override the abstract show method
{
System.Console.WriteLine("Name : " + name);
}
}
class TestEmployeeAndManager
{
static void Main()
{
// Create an instance of Manager and assign it to a Manager reference:
Manager m1 = new Manager("H. Ackerman");
m1.Show();
// Create an instance of Manager and assign it to an Employee reference:
Employee ee1 = new Manager("M. Knott");
ee1.Show(); //call the show method of the Manager class
}
}
このコードは、Manager クラスによって提供された Show() の実装を呼び出し、従業員の名前を画面に出力します。出力は次のとおりです。
Name : H. Ackerman
Name : M. Knott
インターフェイス
インターフェイスは、メソッドのシグネチャは存在しても、メソッドの実装は存在しない一種のスケルトン クラスです。つまり、インターフェイスは、抽象メソッドのみが存在する抽象クラスのようなものです。C# インターフェイスは Java インターフェイスと基本的に同じであり、同じように機能します。
インターフェイスのすべてのメンバは定義上、パブリックです。そのため、インターフェイスには、定数、フィールド (プライベート データ メンバ)、コンストラクタ、デストラクタ、およびあらゆる種類の静的メンバを含めることができません。インターフェイスのメンバに修飾子が指定されている場合は、コンパイル時にエラーが発生します。
インターフェイスを実装するには、そのインターフェイスからクラスを派生させます。このような派生クラスでは、それ自体が抽象項目として宣言されている場合を除き、インターフェイスのすべてのメソッドに実装を提供する必要があります。
インターフェイスは、Java と同じように宣言します。インターフェイス宣言では、プロパティとしてインターフェイスの型と、インターフェイスが読み取り専用か、書き込み専用か、または読み書き可能かを get と set の 2 つのキーワードのみによって指定します。1 つの読み取り専用プロパティを宣言するインターフェイスの例を次に示します。
public interface ICDPlayer
{
void Play(); // method signature
void Stop(); // method signature
int FastForward(float numberOfSeconds);
int CurrentTrack // read-only property
{
get;
}
}
このインターフェイスからクラスを継承するには、Java の implements キーワードの代わりにコロンを使用します。実装するクラスでは、次のように、すべてのメソッドと必要なすべてのプロパティ アクセサの定義を提供する必要があります。
public class CDPlayer : ICDPlayer
{
private int currentTrack = 0;
// implement methods defined in the interface
public void Play()
{
// code to start CD...
}
public void Stop()
{
// code to stop CD...
}
public int FastForward(float numberOfSeconds)
{
// code to fast forward CD using numberOfSeconds...
return 0; //return success code
}
public int CurrentTrack // read-only property
{
get
{
return currentTrack;
}
}
// Add additional methods if required...
}
複数インターフェイスの実装
クラスは、次の構文を使用して複数のインターフェイスを実装できます。
public class CDAndDVDComboPlayer : ICDPlayer, IDVDPlayer
メンバ名があいまいな複数のインターフェイスを実装する場合、クラスは、プロパティ名またはメソッド名の完全な修飾子によって解決されます。言い換えると、派生クラスは、ICDPlayer.Play() のようにインターフェイスの所属先を示すメソッドの完全修飾名を使用して競合を解決できます。