C#の継承:再利用可能で保守しやすいコードを構築する
オブジェクト指向プログラミングにおいて最も重要な概念の1つが継承です。継承により、既存のクラスを用いて新しいクラスを定義できるため、アプリケーションの作成と保守が容易になります。これによりコード機能の再利用が可能になり、実装時間を短縮できます。
クラスを作成する際、完全に新しいデータメンバーとメンバ関数を書く代わりに、既存のクラスのメンバーを継承するように新しいクラスを指定できます。この既存のクラスを基底クラス(base class)、新しいクラスを派生クラス(derived class)と呼びます。
継承の考え方はIS-A関係を実装します。例えば、哺乳類IS A動物、犬IS-A哺乳類、したがって犬IS-A動物でもあります。
基底クラスと派生クラス
クラスは複数のクラスまたはインターフェースから派生できます。つまり、複数の基底クラスまたはインターフェースからデータと関数を継承できます。
派生クラスの構文
<access-specifier> class <base_class> { ... }
class <derived_class> : <base_class> { ... }
基本的な継承の例
using System;
namespace InheritanceApplication {
// 基底クラス
class Shape {
public void SetWidth(int w) { width = w; }
public void SetHeight(int h) { height = h; }
protected int width;
protected int height;
}
// 派生クラス
class Rectangle : Shape {
public int GetArea() {
return (width * height);
}
}
class RectangleTester {
static void Main(string[] args) {
Rectangle rect = new Rectangle();
rect.SetWidth(5);
rect.SetHeight(7);
Console.WriteLine("Total area: {0}", rect.GetArea());
Console.ReadKey();
}
}
}
実行結果:
Total area: 35
この例では、RectangleがShapeからwidth、height、SetWidth、SetHeightを継承しています。protectedメンバーは派生クラスからアクセス可能です。
基底クラスの初期化
派生クラスは基底クラスのメンバ変数とメンバメソッドを継承します。したがって、サブクラスが作成される前にスーパークラスのオブジェクトが作成される必要があります。メンバ初期化リストでスーパークラスの初期化指示を与えます。
基底クラスコンストラクタ呼び出しの例
using System;
namespace RectangleApplication {
class Rectangle {
protected double length;
protected double width;
public Rectangle(double l, double w) {
length = l;
width = w;
}
public double GetArea() { return length * width; }
public void Display() {
Console.WriteLine("Length: {0}", length);
Console.WriteLine("Width: {0}", width);
Console.WriteLine("Area: {0}", GetArea());
}
}
class Tabletop : Rectangle {
private double cost;
// 基底クラスのコンストラクタを呼び出し
public Tabletop(double l, double w) : base(l, w) { }
public double GetCost() {
return GetArea() * 70;
}
public void Display() {
base.Display(); // 基底クラスのDisplayを呼び出し
Console.WriteLine("Cost: {0}", GetCost());
}
}
class ExecuteRectangle {
static void Main(string[] args) {
Tabletop t = new Tabletop(4.5, 7.5);
t.Display();
Console.ReadLine();
}
}
}
実行結果:
Length: 4.5
Width: 7.5
Area: 33.75
Cost: 2362.5
C#での多重継承
C#はクラスの多重継承をサポートしていません。しかし、インターフェースを使用して多重継承のような機能を実現できます。
インターフェースを使用した多重継承の例
using System;
namespace InheritanceApplication {
class Shape {
public void SetWidth(int w) { width = w; }
public void SetHeight(int h) { height = h; }
protected int width;
protected int height;
}
// インターフェース(実装を持たない契約)
public interface PaintCost {
int GetCost(int area);
}
// Shapeから継承し、PaintCostを実装
class Rectangle : Shape, PaintCost {
public int GetArea() {
return (width * height);
}
public int GetCost(int area) {
return area * 70;
}
}
class RectangleTester {
static void Main(string[] args) {
Rectangle rect = new Rectangle();
rect.SetWidth(5);
rect.SetHeight(7);
int area = rect.GetArea();
Console.WriteLine("Total area: {0}", area);
Console.WriteLine("Total paint cost: ${0}", rect.GetCost(area));
Console.ReadKey();
}
}
}
実行結果:
Total area: 35
Total paint cost: $2450
継承に関する重要なキーワード
| キーワード | 説明 |
|---|---|
base | 基底クラスのメンバー(コンストラクタ、メソッド、プロパティ)にアクセス |
virtual | 派生クラスでオーバーライド可能なメソッド/プロパティを宣言 |
override | 基底クラスのvirtualメンバーを派生クラスで再定義 |
sealed | クラスの継承を禁止、またはメソッドの更なるオーバーライドを禁止 |
abstract | インスタンス化不可、派生クラスでの実装を強制 |
new | 基底クラスのメンバーを隠蔽(オーバーライドではない) |
virtual/overrideの例
class Animal {
public virtual void MakeSound() {
Console.WriteLine("何か音を出す");
}
}
class Dog : Animal {
public override void MakeSound() {
Console.WriteLine("ワンワン!");
}
}
sealedの例
sealed class FinalClass { } // これ以上継承不可
class Base {
public virtual void Method() { }
}
class Derived : Base {
public sealed override void Method() { } // 更なるオーバーライド禁止
}
継承 vs 合成(Composition)
「IS-A関係」には継承、「HAS-A関係」には合成を使用するのが原則です。
// IS-A: 継承が適切
class Dog : Animal { }
// HAS-A: 合成が適切
class Car {
private Engine engine; // 車はエンジンを持つ
private List wheels;
}
ベストプラクティス
- 継承は「IS-A関係」が明確に成り立つ場合のみ使用する
- 基底クラスのメソッドをオーバーライドする際は
overrideを明示し、base.Method()で基底実装を呼び出すか判断する - 継承階層を深くしすぎない(3〜4レベルまで推奨)
- 共通の実装を共有する場合は、抽象基底クラスまたはインターフェース+デフォルト実装(C# 8.0+)を検討
- 合成(Composition)を優先し、継承による強い結合を避ける
sealedを活用して、継承を意図しないクラスを保護する
C言語
- C# クラスとオブジェクト:堅牢な OOP の基礎
- C# static キーワード:静的変数、メソッド、クラスをマスターする
- C# 入れ子クラスを理解する:定義、使用法、継承
- C++クラステンプレート完全ガイド:実践的なジェネリックプログラミング
- C#プログラム構造の理解:コアコンポーネントと例
- C#基礎:基本構文とオブジェクト指向概念をマスター
- Python継承マスター:概念、構文、実践例
- Java無名インナークラス完全ガイド:定義、構文、実践例
- Java ObjectOutputStream完全ガイド:シリアル化、メソッド、実践例
- Javaジェネリクス完全ガイド:再利用可能で型安全なコードの構築
- Javaファイル操作のマスター:java.ioによる作成・読み取り・書き込み・削除