インターフェースメンバーの既定の実装
《 初回公開:2020/06/28 , 最終更新:未 》
visual studio2019 C#8.0で動作確認。
【 目次 】
インターフェースメンバーのデフォルトの実装
C# 8.0より、インターフェースメンバーのデフォルトの実装が可能となった。
インターフェイスには、名前空間またはクラスのメンバーを指定できます。 インターフェイス宣言には、次のメンバーの宣言を含めることができます (実装なしのシグネチャ)。
- メソッド
- Properties
- インデクサー
- イベント
これらのメンバー宣言には通常、本文が含まれません。
C# 8.0 以降では、インターフェイス メンバーで本文を宣言できます。
これは既定の実装と呼ばれています。 本文のあるメンバーでは、実装がオーバーライドされないクラスおよび構造体に "既定の" 実装を与えることがインターフェイスに許可されます。
また、C# 8.0 以降、インターフェイスには次を含めることができます。
- 定数
- オペレーター
- 静的コンストラクター。
- 入れ子にされた型
- 静的フィールド、メソッド、プロパティ、インデクサー、イベント
- 明示的なインターフェイス実装構文を使用するメンバー宣言。
- 明示的なアクセス修飾子 (既定のアクセスは public)。
C# 8.0 (.NET Core 3.0)で、インターフェイスの制限が緩和されました。 以下のようになります。
- メソッド、プロパティ、インデクサー、イベントのアクセサーの実装を持てるようになった
- アクセシビリティを明示的に指定できるようになった
- 静的メンバーを持てるようになった
これら指して「インターフェイスのデフォルト実装」(default implementations of interfaces)と呼びます※。 (1番目の「インターフェイスが関数メンバーの実装を持てる」というのを主目的に検討されたもので、 言葉の意味だけからすると、狭義にはこの1番目の機能こそが「デフォルト実装」です。 ただ、これのついでに実装されたものなので2番目、3番目には具体的な名前がついていません。)
インターフェイスの制限が緩和されたが、クラスと異なり多重継承が可能な代わりに、「状態を持つ」事になるフィールドメンバーは許されないようだ。
「状態を持つ」というのは、C#でいうとメンバーとしてフィールドを持てるかどうかである。
フィールドを持つということは、メモリ上のどこかにデータを記憶しておく領域が必要になる。後述するように、多重継承が絡むとデータの記憶領域のレイアウトが複雑になるため、クラスとインターフェースに分けることで複雑さを回避している。
すなわち、C#にインターフェースのデフォルト実装を導入するに当たっては、C#だけではなく、.NETランタイムに手を加える必要がある。
現在、インターフェースが実装を持てないという制限を掛けているのは.NETランタイムであり、この制限を取り払う修正が必要になる。
...
要するに、インスタンスフィールドを持てない以外はほとんど抽象クラスと同じである。
デフォルトインターフェースメソッドはトレイトの機能として役立つみたいな
トレイトについてはPHPのマニュアルの方が具体的コードがでていてわかりやすいかな。
どんな事ができるのか試してみる
インターフェースの既定の実装で、どんな事ができるのだろうか?
とりあえずこんな事ができるのかな、という事で適当なコードを書いてみた。
interface MyInterface { protected static int _i; public int DI { get => _i * _i; } } class MyClass : MyInterface { public MyClass(int i) { MyInterface._i = i; } } private static void Test() { MyInterface p = new MyClass(20); Console.WriteLine($"p.DI = {p.DI}"); }
フィールドメンバーは許されないと言っても、staticなフィールドは許されるようで、
ここではMyInterfaceはstaticなフィールド変数_i
と、デフォルトの読み取り専用のプロパティDI
をメンバーとして持つ。
実行結果
p.DI = 400
デフォルトの実装の上書き
もう少し詳しくみていく。
インターフェイスのデフォルトのメソッドを定義
interface IShowrable { void ShowMe() => Console.WriteLine("IShowrableインターフェースのデフォルトのShowMe"); }
インターフェイスを実装する2つのクラスを定義。
後者のクラスはデフォルトのメソッドを上書き。
class MyShowrable1 : IShowrable { } class MyShowrable2 : IShowrable { public void ShowMe() => Console.WriteLine("デフォルトのShowMeの上書き"); }
クラスのインスタンスを生成してメソッドを呼び出す。
var myShowrable1 = new MyShowrable1(); // 次の行はエラー => MyShowrable2クラスのShowMeメソッドは存在しない // myShowrable1.ShowMe(); ((IShowrable)myShowrable1).ShowMe(); var myShowrable2 = new MyShowrable2(); // 次の行はOK myShowrable2.ShowMe(); ((IShowrable)myShowrable2).ShowMe();
実行結果
IShowrableインターフェースのデフォルトのShowMe デフォルトのShowMeの上書き デフォルトのShowMeの上書き
上書きされたメソッドはクラスのメソッドとしても実行可能。
では、後者のクラスに更にインターフェースの明示的実装によるメソッドを追加した場合は
class MyShowrable3 : IShowrable { public void ShowMe() => Console.WriteLine("MyShowrable3クラスのShowMeのメソッド"); void IShowrable.ShowMe() => Console.WriteLine("デフォルトのShowMeの上書き(明示的実装)"); }
var myShowrable3 = new MyShowrable3(); // 次の行はMyShowrable3クラスのShowMeメソッド myShowrable3.ShowMe(); ((IShowrable)myShowrable3).ShowMe();
実行結果
MyShowrable3クラスのShowMeメソッド デフォルトのShowMeの上書き(明示的実装)
同名のデフォルトの実装メソッドを持つ新しいインターフェースを定義して
interface IOrther { void ShowMe() => Console.WriteLine("IOrtherインターフェースのデフォルトのShowMe"); }
2つのインターフェースを実装するクラスを定義。
片方のメソッドだけを上書き。
class MultiSharable : IShowrable, IOrther { void IShowrable.ShowMe() => Console.WriteLine("デフォルトのShowMeの上書き"); }
これを呼び出すと
var multiIShowrable = new MultiIShowrable(); // 次の行はエラー => MultiIShowrableクラスのShowMeメソッドは存在しない //multiIShowrable.ShowMe(); ((IShowrable)multiIShowrable).ShowMe(); ((IOrther)multiIShowrable).ShowMe();
実行結果
IShowrableインターフェースのデフォルトのShowMeの上書き IOrtherインターフェースのデフォルトのShowMe
デフォルトインターフェースメソッドを上書きしないIOrtherのShowMeメソッドでは、IOrtherのデフォルトの実装メソッドが呼び出され、デフォルトインターフェースメソッドを上書きしたIShowrableのShowMeメソッドでは、MultiIShowrableクラスで明示的な実装で上書きしたIShowrable.ShowMeメソッドが呼び出される事になる。