はじめに
誤りについては優しく指摘してくださると嬉しいです。
早見表
| 抽象メソッド | 仮想メソッド | インターフェース | |
|---|---|---|---|
| インスタンス化 | × | 〇 | × |
| デフォルト実装 | × | 〇必須 | 〇任意 |
| オーバーライド | 〇必須 | △任意 | ×各自で実装 |
| プロパティ | 共通 | 共通 | 各クラスで定義 |
| フィールド変数 | 〇 | 〇 | ×定数のみ |
| コンストラクタ | 〇 | 〇 | × |
| 多重継承 | × | × | 〇複数実装可 |
| 用途 | 特定の処理を強制 is-a関係 |
明確なデフォルト有 is-a関係 |
共通の振る舞いを持つ 状態を共有しない |
抽象メソッド
abstractをつけることで抽象クラス・抽象メソッドを定義できる。
抽象クラスはインスタンスを生成できないという特徴があり、継承を前提として使われる。
abstractがついたメソッドはoverrideで継承先で再定義できる。
継承先で必ず実装したいメソッドがある場合などに有効。
public abstract class Animal // 抽象クラス
{
public string _name = "名無し"; // フィールド:継承される
public abstract string Sound { get; } // プロパティ:実装を強制、デフォルト値不可
public abstract void Speak(); // 実装を強制、デフォルト実装不要
public void Sleep() => Console.WriteLine($"{_name}: zzz..."); // 非抽象メソッドも持てる
}
public class Dog : Animal
{
public override string Sound { get; } = "ワン";
public override void Speak()
=> Console.WriteLine($"{_name}: {Sound}");
}
public class Cat : Animal
{
public override string Sound { get; } = "ニャー";
public override void Speak()
=> Console.WriteLine($"{_name}: {Sound}!");
}
// 呼び出し
Animal d = new Dog { _name = "ポチ" };
Animal c = new Cat { _name = "ミケ" };
d.Speak(); // ポチ: ワン
c.Speak(); // ミケ: ニャー!
d.Sleep(); // ポチ: zzz...
仮想メソッド
virtualをつけることで仮想メソッドを定義できる。
virtualがついたメソッドはoverrideで継承先で再定義できる。
仮想メソッドは代入される変数の型 (=の左側) ではなく、代入する側の型 (=の右側) に基づいて呼ばれる。
つまり仮想メソッドを使えば、同じメソッドを呼んだとしても格納されているインスタンスの型によって異なる動作を行う。
public class Animal
{
public string _name = "名無し"; // フィールド:継承される
public virtual string Sound { get; } = "..."; // プロパティ:オーバーライド可能
public virtual void Speak() // デフォルト実装あり
=> Console.WriteLine($"{_name}: {Sound}");
}
public class Dog : Animal
{
public override string Sound { get; } = "ワン";
// Speak()はオーバーライドしなくてもよい(基底クラスの実装が使われる)
}
public class Cat : Animal
{
public string _name = "ミケ"; // 基底の_nameを隠蔽する
public override string Sound { get; } = "ニャー";
public override void Speak() // 独自にオーバーライドも可能
=> Console.WriteLine($"{_name} {Sound}!");
}
// 呼び出し
Animal d = new Dog { _name = "ポチ" };
Animal c = new Cat();
d.Speak(); // ポチ: ワン
c.Speak(); // ミケ ニャー!
overrideされたメソッドはさらに派生クラスでoverride可能。
また、base.メソッド名()で親クラスの実装を呼び出せる。
ただし抽象メソッドをbase経由で呼ぶと実行時エラーになる。
public abstract class Animal
{
public string _name = "名無し";
public abstract string Sound { get; }
public abstract void Speak();
public virtual void Sleep() // virtualなので base呼び出し可能
=> Console.WriteLine($"{_name}: zzz...");
}
public class Dog : Animal
{
public override string Sound { get; } = "ワン";
public override void Speak()
=> Console.WriteLine($"{_name}: {Sound}");
public override void Sleep()
{
base.Sleep(); // Animal.Sleep() を呼び出す → "ポチ: zzz..."
Console.WriteLine($"{_name}: むにゃむにゃ..."); // 追加の処理
}
}
public class GoldenRetriever : Dog
{
public override string Sound { get; } = "ワンワン";
public override void Sleep()
{
base.Sleep(); // Dog.Sleep() を呼び出す(Animal.Sleep()ではない)
Console.WriteLine($"{_name}: ぐーぐー...");
}
}
// 呼び出し
Animal d = new Dog { _name = "ポチ" };
Animal g = new GoldenRetriever { _name = "ゴールド" };
d.Speak(); // ポチ: ワン
d.Sleep(); // ポチ: zzz...
// ポチ: むにゃむにゃ...
g.Speak(); // ゴールド: ワンワン
g.Sleep(); // ゴールド: zzz... ← base.Sleep()経由でAnimal.Sleep()が呼ばれる
// ゴールド: むにゃむにゃ... ← base.Sleep()経由でDog.Sleep()の処理が呼ばれる
// ゴールド: ぐーぐー...
インターフェース
クラスが実装する規約を定めたもの。
抽象メソッドのみを持つ抽象クラスのようなイメージ。
public interface IAnimal
{
// フィールド変数は定義不可(定数のみ可)
const string Category = "動物"; // 定数はOK
string Name { get; set; } // プロパティ:実装を強制(C#8以降はデフォルト実装も可)
string Sound { get; }
void Speak();
void Sleep() => Console.WriteLine($"{Name}: zzz...");
}
public class Dog : IAnimal
{
public string Name { get; set; } = "名無し";
public string Sound { get; } = "ワン";
public void Speak()
=> Console.WriteLine($"{Name}: {Sound}");
}
public class Cat : IAnimal
{
public string Name { get; set; } = "名無し";
public string Sound { get; } = "ニャー";
public void Speak()
=> Console.WriteLine($"{Name}: {Sound}!");
public void Sleep() // デフォルト実装を独自にオーバーライド
=> Console.WriteLine($"{Name}: すやすや...");
}
// 呼び出し(複数インターフェイスを同じ型として扱える)
IAnimal d = new Dog { Name = "ポチ" };
IAnimal c = new Cat { Name = "ミケ" };
d.Speak(); // ポチ: ワン
c.Speak(); // ミケ: ニャー!
d.Sleep(); // ポチ: zzz...
c.Sleep(); // ミケ: すやすや...
Console.WriteLine(IAnimal.Category); // 動物
// IAnimal型経由でないとSleep()は呼び出せない
Dog d2 = new Dog{ Name = "ポチ" };
d2.Sleep(); // エラー
インターフェース名をメンバー名に付与することで、クラスのインスタンスからは
直接アクセスできないが、インターフェース型経由ではアクセスできるような挙動にもできる。
// 明示的インターフェース実装
// インターフェース名.メンバー名 の形で実装する(アクセス修飾子は書けない)
public class Bird : IAnimal
{
public string Name { get; set; } = "名無し";
public string Sound { get; } = "チュン";
void IAnimal.Speak() // 明示的実装
=> Console.WriteLine($"{Name}: {Sound}");
}
Bird b = new Bird { Name = "ピー" };
// b.Speak(); // エラー:クラス型経由では呼べない
((IAnimal)b).Speak(); // OK:インターフェース型にキャストすれば呼べる
参考