インターフェースメンバーの既定の実装

《 初回公開: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メソッドが呼び出される事になる。

ページのトップへ戻る