継承(inheritance)とはオブジェクト指向の中核を担う概念で、 あるクラスから性質を受け継いだ新しいクラスを作ることです。 継承は派生(derivation)とも呼ばれます。
継承関係の例として、「人間」と「学生」という2つのクラスについて考えて見ましょう。
「学生」は「人間」の一部です。 すなわち、「学生」ならば必ず「人間」としての特徴を備えています。 それとは逆に「人間」だからといって必ずしも「学生」であるとはいえません。 つまり、「学生」は「人間」の特別な場合である(「人間⊃学生」という包含関係が成り立つ)といえます。
例えば、「人間」には「名前」、「年齢」などの属性があります。 (ここでは簡単化のためこの2つの属性のみを考えます。) 「学生」は人間の一部分ですから、当然この2つの属性を備えています。 それに加え、「学生」は「学籍番号」という属性を持っています。
このように、あるクラス A がクラス B を包含するような関係にあるとき、 この関係を継承関係と呼び、 「B は A を継承する」とか「B は A から派生する(導出される)」といいます。 また、このとき、クラス A のことを「基底クラス(base class)」 または「スーパークラス(super class)」と呼び、 クラス B のことを「派生クラス(derived class)」 または「サブクラス(sub class)」と呼びます。
C# を始めとするオブジェクト指向言語では、 このような継承関係を表現するため、 あるクラスが他のクラスを継承するための構文が用意されています。 C# でクラスの継承を行うためには、クラス定義の際に以下のように書きます。
class 派生クラス名 : 基底クラス名 { 派生クラスの定義 }
クラスの継承の例として、先ほどの「人間」と「学生」にあたるクラス
Person と Student を
C# でクラス化すると以下のようになります。
class Person { public string name; // 名前 public int age; // 年齢 } class Student : Person { public int id; // 学籍番号 }
クラス利用側のコードは以下のようになります。
Person p1 = new Person(); p1.name = "天野舞耶"; p1.age = 23; Student s1 = new Student(); s1.name = "周防達也"; // Person のメンバをそのまま利用出来る s1.age = 18; s1.id = 50012; Person p2 = s1; // Student は Person として扱うことが出来る。 Student s2 = p1; // でも、Person は Student として扱っちゃ駄目。 //↑この1行はエラーになる。
C# では、派生クラスのインスタンスは基底クラスの変数に代入することが出来ます。 これは、例えば、学生ならば必ず人間であるため、「学生は人間として扱うことができる」ということです。 それとは逆に、基底クラスのインスタンスを派生クラスの変数に代入することは出来ません。 すなわち、すべて人間が学生というわけではないですから、「人間を無条件に学生として扱ってはいけない」ということです。
C# では、基底クラスを指定せずに作成した型は全て自動的に object 型を継承することになります。
(構造体等の値型は明示的に他の型を継承できないので、必ず object を継承します。)
つまり、C# における全ての型は object の派生クラスになります。
object 型には Equals (他のインスタンスとの比較)や ToString (インスタンスを文字列化する)等の機能があります。
派生クラスのインスタンスが生成される際、 派生クラスのコンストラクタが呼び出される前に 基底クラスのコンストラクタが呼び出されます。
例えば、以下のようなコードを実行すると、
まず、Base クラスのコンストラクタが呼ばれ、
その後 Derived クラスのコンストラクタが呼ばれます。
using System; class Base { public Base() { Console.Write("base constractor called\n"); } } class Derived : Base { public Derived() { Console.Write("derived constractor called\n"); } } class InheritTest { public static void Main() { Derived d = new Derived(); } }
base constractor called derived constractor called
先ほど行ったように、派生クラスのインスタンスを生成する際、 自動的に基底クラスのコンストラクタも呼び出されます。 しかし、この際、呼び出されるコンストラクタは引数なしのコンストラクタになります。
基底クラスの引数つきのコンストラクタを呼び出すためには、 以下のように自分でコードを書いて明示的に基底クラスのコンストラクタを呼び出す必要があります。
派生クラスのコンストラクタ(引数) : base(基底クラスに渡したい引数) { }
例として、先ほどの Person クラスと Student クラスにコンストラクタを追加してみましょう。
ついでに実装の隠蔽も行った結果を以下に示します。
class Person { private string name; // 名前 private int age; // 年齢 public Person(string name, int age) { this.name = name; this.age = age; } public string Name { set{this.name = value;} get{return this.name;} } public string Age { set{this.age = value;} get{return this.age;} } } class Student : Person { private int id; // 学籍番号 public Student(string name, int age, int id) : base(name, age) { this.id = id; } public int Id { set{this.id = value;} get{return this.id;} } }
「実装の隠蔽」 で、クラスのメンバのアクセスレベルについて説明しました。その際、public と private については説明しましたが、ここでは継承と関係の深い protected について説明します。
public はクラス内外とわずどこからでもアクセス可能なレベルで、 private はクラス内部からのみアクセス可能なレベルです。 これらに対し、protected はクラスとそのクラスを継承する派生クラス内からアクセス可能なレベルです(private は派生クラス内からアクセスできない)。 以下に例を挙げます。
class Base { public int public_val; protected int protected_val; private int private_val; void BaseTest() { public_val = 0; // OK protected_val = 0; // OK private_val = 0; // OK } } class Derived : Base { void DerivedTest() { public_val = 0; // OK protected_val = 0; // OK (protected は派生クラスからアクセス可能) private_val = 0; // エラー(private は派生クラスからアクセス不能) } } class Test { public static void Main() { Base b = new Base(); b.public_val = 0; // OK b.protected_val = 0; // エラー(protected は外部からアクセス不能) b.private_val = 0; // エラー(private は外部からアクセス不能) } }
派生クラスには自由にメンバを追加することが出来ますが、 基底クラスの public メンバと同名のメンバを再定義してしまうと 基底クラスのメンバが新しく追加されたメンバに隠れてしまいます。 このような状態を「基底クラスのメンバを隠蔽する」といいます。
using System; class Base { public void Test() { Console.Write("Base.Test()\n"); } } class Derived : Base { public void Test() //基底クラスの Test() と同名のメソッド { Console.Write("Derived.Test()\n"); } } class Test { public static void Main() { Base b = new Base(); b.Test(); // Base の Test が呼ばれる Derived d = new Derived(); d.Test(); // Derived の Test が呼ばれる ((Base)d).Test(); // Base に キャストしてから Test を呼ぶと Base の Test が呼ばれる } }
Base.Test() Derived.Test() Base.Test()
ここで、プログラマが意図して基底クラスのメンバの隠蔽を行う分には何の問題もないんですが、 基底クラスに同名のメソッドがあることに気づかずにメソッドを追加してしまうと、 意図しない動作を引き起こしてしまうことがあります。 そこで、C#では基底クラスのメンバの隠蔽を行う場合、メソッドにnew修飾子を付ける必要があります。 (new修飾子を付けていない場合、コンパイラが警告を出します。)
class Derived : Base { //基底クラスのメンバを隠蔽するには new を付ける必要がある。 public new void Test() { Console.Write("Derived.Test()\n"); } }
ちなみに、以下のように、base キーワードを使用すれば、 隠蔽されてしまった基底クラスのメンバを参照することができます。
class Base { public void Test() { Console.Write("Base.Test()\n"); } } class Derived : Base { public new void Test() //基底クラスの Test() と同名のメソッド { Console.Write("Derived.Test()\n"); } public void Test2() { this.Test(); // Derived の Test が呼ばれる。 base.Test(); // Base の Test が呼ばれる。 } }
C# のクラスは基本的に常に継承して派生クラスを作ることができるのですが、 場合によっては絶対に継承されたくないと言うこともあります。 このような場合、クラス定義時に sealed (封印された)というキーワードをつけることで、 継承を禁止することができます。
sealed class SealedClass { } class Derived : SealedClass // SealedClass は継承不可なので、エラーになる。 { }