次の方法で共有


既定のインターフェイス メソッド

メモ

この記事は機能仕様についてです。 仕様は、機能の設計ドキュメントとして使用できます。 これには、提案された仕様の変更および機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と行われた実装では、いくつかの違いがあることがあります。 これらの違いは、関連する言語設計ミーティング (LDM) メモに取り上げられています。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

チャンピオンの課題: https://github.com/dotnet/csharplang/issues/52

まとめ

仮想拡張メソッド - 具象実装を持つインターフェイス内のメソッドのサポートが追加されました。 このようなインターフェイスを実装するクラスまたは構造体には、そのクラスまたは構造体自身によって実装されるか、基底クラスまたはインターフェイスから継承される形で、インターフェイス メソッドに対する最も具体的な 実装が 1 つ 必要です。 仮想拡張メソッドを使用すると、API 作成者は、そのインターフェイスの既存の実装とのソースまたはバイナリの互換性を損なうことなく、将来のバージョンでインターフェイスにメソッドを追加できます。

これらは、Javaの「既定のメソッド」に似ています。

(可能性のある実装手法に基づいて) この機能には、CLI/CLR での対応するサポートが必要です。 この機能を利用するプログラムは、以前のバージョンのプラットフォームでは実行できません。

目的

この機能の主な動機は次のとおりです。

  • 既定のインターフェイス メソッドを使用すると、API 作成者は、そのインターフェイスの既存の実装とのソースまたはバイナリの互換性を損なうことなく、将来のバージョンでインターフェイスにメソッドを追加できます。
  • この機能により、C# は同様の機能をサポートする Android (Java) および iOS (Swift) を対象とする API と相互運用できるようになります。
  • 結局のところ、既定のインターフェイス実装を追加すると、「特性」言語機能 (https://en.wikipedia.org/wiki/Trait_(computer_programming)) の要素が提供されます。 特性は強力なプログラミング手法であることが証明されています (http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf)。

詳細な設計

インターフェイスの構文は拡張されて、許可するようになっている。

  • 定数、演算子、静的コンストラクター、および入れ子になった型を宣言するメンバー宣言。
  • の本文は、メソッド、インデクサー、プロパティ、またはイベント アクセサー (つまり、「既定」の実装) に対するものです。
  • 静的フィールド、メソッド、プロパティ、インデクサー、およびイベントを宣言するメンバー宣言。
  • 明示的なインターフェイス実装構文を用いたメンバーの宣言。
  • 明示的なアクセス修飾子 (既定のアクセスは public)。

本文を持つメンバーにより、インターフェイスは、独自の実装を提供しないクラスおよび構造体内のメソッドに対して「既定」の実装を提供できるようになります。

インターフェイスには、インスタンスの状態を含めることはできません。 静的フィールドが許可されるようになりましたが、インスタンス フィールドはインターフェイスでは許可されません。 インスタンスの自動プロパティでは暗黙的に隠しフィールドが宣言されることがあり、インターフェイスではサポートされていません。

静的メソッドとプライベート メソッドを使用すると、インターフェイスのパブリック API を実装するために使用されるコードの便利なリファクタリングと整理が可能になります。

インターフェイス内のメソッドのオーバーライドでは、明示的なインターフェイス実装構文を使用する必要があります。

variance_annotation で宣言された型パラメーターのスコープ内でクラス型、構造体型、または列挙型を宣言するとエラーになります。 たとえば、次の C 宣言はエラーです。

interface IOuter<out T>
{
    class C { } // error: class declaration within the scope of variant type parameter 'T'
}

インターフェイスの具象メソッド

この機能の最も簡単な形式は、インターフェイスで具体的なメソッドを宣言する機能です。これは、本体を持つメソッドです。

interface IA
{
    void M() { WriteLine("IA.M"); }
}

このインターフェイスを実装するクラスは、その具体的なメソッドを実装する必要はありません。

class C : IA { } // OK

IA i = new C();
i.M(); // prints "IA.M"

クラス IA.MC の最終オーバーライドは、Mで宣言されている具体的なメソッド IA です。 クラスはインターフェイスからメンバーを継承しないことに注意してください。これは、この機能によって変更されません。

new C().M(); // error: class 'C' does not contain a member 'M'

インターフェイスのインスタンス メンバー内で、this は外側のインターフェイスの型を持ちます。

インターフェイスの修飾子

インターフェイスの構文ルールは、メンバーに修飾子を使用できるように緩和するために調整されています。 許可されているのは、privateprotectedinternalpublicvirtualabstractsealedstaticextern、および partial です。

virtual または sealed 修飾子が使用されない場合、宣言に本体を含むインターフェイス メンバーは private メンバーです。 virtual 修飾子は、通常なら暗黙的に virtual になる関数メンバーで使用される場合があります。 同様に、abstract は本文のないインターフェイス メンバーの既定値ですが、その修飾子を明示的に指定できます。 非仮想メンバーは、sealed キーワードを使用して宣言できます。

これは、インターフェイスの本文が無い private または sealed 関数メンバーのエラーです。 private 関数メンバーには、修飾子の sealed がない場合があります。

アクセス修飾子は、許可されているすべての種類のメンバーのインターフェース メンバーで使用できます。 アクセス レベル public は既定ですが、明示的に指定できます。

未解決の問題: protectedinternal などのアクセス修飾子の正確な意味を指定し、どの宣言がそれらをオーバーライドするか (派生インターフェース内)、またはそれらを実装するか (インターフェースを実装するクラス内) を指定する必要があります。

インターフェイスでは、入れ子になった型、メソッド、インデクサー、プロパティ、イベント、静的コンストラクターなど、static メンバーを宣言できます。 すべてのインターフェイス メンバーの既定のアクセス レベルは public です。

インターフェイスでは、インスタンス コンストラクター、デストラクター、またはフィールドを宣言できません。

終了した問題: インターフェイスで演算子宣言を許可する必要がありますか? おそらく変換演算子ではありませんが、他の演算子はどうでしょうか? 決定: 演算子は、変換演算子、等値演算子、および不等値演算子を除き、許可されます。

終了した問題: 基本インターフェイスからメンバーを非表示にするインターフェイス メンバー宣言で new を許可する必要がありますか? 決定: はい。

終了した問題: 現在、インターフェースまたはそのメンバーの partial は許可されていません。 それには別の提案が必要になります。 決定: はい。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface

インターフェイスでの明示的な実装

明示的な実装により、プログラマーは、コンパイラーまたはランタイムが見つけられないようなインターフェイス内の仮想メンバーの最も具体的な実装を提供できるようになります。 実装宣言は、インターフェイス名で宣言を修飾することによって、特定の基本インターフェイス メソッドを明示的に実装できます (この場合、アクセス修飾子は許可されません)。 暗黙的な実装は許可されません。

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    void IA.M() { WriteLine("IB.M"); } // Explicit implementation
}
interface IC : IA
{
    void M() { WriteLine("IC.M"); } // Creates a new M, unrelated to `IA.M`. Warning
}

インターフェイスでの明示的な実装は、sealed を宣言できません。

インターフェイスのパブリック virtual 関数メンバーは、派生インターフェイスでのみ明示的に実装できます (最初にメソッドを宣言したインターフェイス型で宣言内の名前を修飾し、アクセス修飾子を省略します)。 メンバーは、実装される場所でアクセスできる必要があります。

再抽象化

インターフェイスで宣言された仮想 (具象) メソッドは、派生インターフェイスで再構成できます。

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    abstract void IA.M();
}
class C : IB { } // error: class 'C' does not implement 'IA.M'.

abstract 修飾子は、IB.M が再抽象化されていることを示すために、IA.Mの宣言で必要です。

これは、メソッドの既定の実装が不適切であり、クラスを実装することによってより適切な実装を提供する必要がある派生インターフェイスで役立ちます。

最も具体的な実装規則

すべてのインターフェースとクラスには、型またはその直接および間接的インターフェイスに現れる実装のうちすべての仮想メンバーに対して最も具遺体的な実装が必要です。 最も具体的な実装は、他のすべての実装よりも具体的な固有の実装です。 実装が存在しない場合、メンバー自身が最も具体的な実装と見なされます。

M1 が、T1 の型で宣言され、M2T2 の型で宣言された場合は、1 つの実装は、他の実装よりもM2より具体的M1であると見なされます。

  1. T1 には、その直接または間接インターフェイスの中に T2 を含みます、または
  2. T2 はインターフェイス型ですが、T1 はインターフェイス型ではありません。

次に例を示します。

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
    void IA.M() { WriteLine("IC.M"); }
}
interface ID : IB, IC { } // compiles, but error when a class implements 'ID'
abstract class C : IB, IC { } // error: no most specific implementation for 'IA.M'
abstract class D : IA, IB, IC // ok
{
    public abstract void M();
}
public class E : ID { } // Error. No most specific implementation for 'IA.M'

最も具体的な実装規則により、競合 (つまり、ダイヤモンドの継承から生じるあいまいさ) が、競合が発生した時点でプログラマによって明示的に解決されます。

インターフェースで明示的な再抽象化をサポートしているため、クラスでも同様に行うことができます。

abstract class E : IA, IB, IC // ok
{
    abstract void IA.M();
}

終了した問題: クラスでの明示的なインターフェイス抽象実装をサポートする必要がありますか? 決定: いいえ

さらに、クラス宣言で、一部のインターフェイス メソッドの最も具体的な実装が、インターフェイスで宣言された抽象実装である場合はエラーになります。 これは、新しい用語を使用して修正された既存のルールです。

interface IF
{
    void M();
}
abstract class F : IF { } // error: 'F' does not implement 'IF.M'

インターフェイスで宣言された仮想プロパティには、1 つのインターフェイスで get アクセサーの最も具体的な実装と、別のインターフェイスでの set アクセサーの最も具体的な実装が可能です。 これは、最も具体的な実装規則の違反と見なされ、コンパイラ エラーが生成されます。

static および private メソッド。

インターフェースには実行可能コードが含まれるようになったため、共通コードをプライベート メソッドと静的メソッドに抽象化すると便利です。 ここでは、インターフェイスでこれらを許可します。

終了した問題: プライベート メソッドをサポートする必要がありますか? 静的メソッドをサポートする必要がありますか? 決定: はい

未解決の問題: インターフェイスメソッドを protected または internal またはその他のアクセスを許可する必要がありますか? その場合、セマンティクスは何ですか? デフォルトで virtual ですか? その場合、非仮想にする方法はありますか?

終了した問題: 静的メソッドをサポートする場合は、(静的) 演算子をサポートする必要がありますか? 決定: はい

基本インターフェイスの呼び出し

このセクションの構文は実装されていません。 これはアクティブな提案のままです。

既定のメソッドを持つインターフェイスから派生した型のコードは、そのインターフェイスの「基本」実装を明示的に呼び出すことができます。

interface I0
{
   void M() { Console.WriteLine("I0"); }
}
interface I1 : I0
{
   override void M() { Console.WriteLine("I1"); }
}
interface I2 : I0
{
   override void M() { Console.WriteLine("I2"); }
}
interface I3 : I1, I2
{
   // an explicit override that invoke's a base interface's default method
   void I0.M() { I2.base.M(); }
}

インスタンス (非静的) メソッドは、構文 base(Type).M を使用して名前を付けることで、直接基本インターフェイスでアクセス可能なインスタンス メソッドの実装を非仮想的に呼び出すことができます。 これは、ダイヤモンドの継承のために指定する必要があるオーバーライドが、特定の基本実装への委任によって解決される場合に便利です。

interface IA
{
    void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
    override void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
    override void IA.M() { WriteLine("IC.M"); }
}

class D : IA, IB, IC
{
    void IA.M() { base(IB).M(); }
}

base(Type).M の構文を使用して、virtual または abstract メンバーにアクセスする場合、Type に、M 向けの固有の最も具体的なオーバーライドが含まれる必要があります。

基本句の結合

インターフェイスに型が含まれるようになりました。 これらの型は、ベースインターフェースとして基本句で使用できます。 基本句をバインドする場合、それらの型をバインドするための基本インターフェイスのセットを知る必要がある場合があります (たとえば、それらを検索して保護されたアクセスを解決するため)。 したがって、インターフェイスの基本句の意味は循環的に定義されます。 サイクルを中断するために、クラスに対して既に設定されている同様のルールに対応する新しい言語ルールを追加します。

インターフェイスの interface_base の意味を決定する際に、基本インターフェイスは一時的に空であると見なされます。 直感的に、これにより、基本句の意味が再帰的にそれ自体に依存できなくなります。

以前は次のようなルールがありました:

クラス B がクラス A から派生する場合、A が B に依存するとコンパイル時エラーになります。クラスは、その直接の基底クラス (存在する場合) に直接依存し、そのクラスが直接入れ子にされているクラス (存在する場合) にも直接依存します。 この定義を考慮すると、クラスが依存するクラスの完全なセットは、関係に直接依存する反射的かつ推移的なクロージャ (関数閉包) です。

インターフェイスが直接的または間接的に自身を継承すると、コンパイル時にエラーが発生します。 インターフェイスの基本インターフェイスは、明示的な基本インターフェイスとその基本インターフェイスです。 つまり、基本インターフェース群は、明示的な基本インターフェースとそれらの明示的な基本インターフェースの完全な推移閉包です。

次のように調整しています:

クラス B がクラス A から派生した場合、B に依存する A に対するコンパイル時のエラーです。A クラスは、その直接基底クラス (ある場合) 直接依存し、ネスト内 (ある場合) で、直接依存します。

インターフェイス IB がインターフェイス IA を拡張する場合、IA が IB に依存するとコンパイル時エラーになります。 インターフェイス は、直接基本インターフェイス (存在する場合) に依存し、そして は、直下にネストされる型 (存在する場合) に直接依存します。

これらの定義を考慮すると、型が依存する 型の完全なセット は、が直接依存する 関係の反射的かつ推移的クロージャです。

既存のプログラムへの影響

ここで提示されるルールは、既存のプログラムの意味に影響を与えないことを目的としています。

例 1:

interface IA
{
    void M();
}
class C: IA // Error: IA.M has no concrete most specific override in C
{
    public static void M() { } // method unrelated to 'IA.M' because static
}

例 2:

interface IA
{
    void M();
}
class Base: IA
{
    void IA.M() { }
}
class Derived: Base, IA // OK, all interface members have a concrete most specific override
{
    private void M() { } // method unrelated to 'IA.M' because private
}

同じ規則は、既定のインターフェイス メソッドを含む類似の状況にも同様の結果をもたらします。

interface IA
{
    void M() { }
}
class Derived: IA // OK, all interface members have a concrete most specific override
{
    private void M() { } // method unrelated to 'IA.M' because private
}

終了した問題: これが仕様の意図した結果であることを確認します。 決定: はい

ランタイム メソッドの解決

終了した問題: 仕様では、インターフェイスの既定のメソッドに直面してランタイム メソッド解決アルゴリズムを記述する必要があります。 セマンティクスが言語セマンティクスと一致していることを確認する必要があります。たとえば、宣言されたメソッドが internal メソッドをオーバーライドまたは実装するかどうかなどです。

CLR 対応 API

コンパイラがこの機能をサポートするランタイム用にコンパイルしていることを検出できるように、そのようなランタイム用のライブラリは、https://github.com/dotnet/corefx/issues/17116 で説明されている API を介してその事実を通知するように変更されています。 追加する

namespace System.Runtime.CompilerServices
{
    public static class RuntimeFeature
    {
        // Presence of the field indicates runtime support
        public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation);
    }
}

未対応問題: これは、CLR 機能に最適な名前ですか? CLR 機能は、それだけではありません (たとえば、保護制約の緩和、インターフェイスでのオーバーライドのサポートなど)。 おそらく、「インターフェイス内の具体的なメソッド」や「特性」などと呼ぶべきでしょうか?

指定すべきその他の領域

  • [ ] 既定のインターフェイス メソッドとオーバーライドを既存のインターフェイスに追加することで、発生するソースとバイナリの互換性効果の種類をカタログ化すると便利です。

デメリット

この提案には、CLR 仕様の調整された更新が必要です (インターフェイスとメソッド解決の具体的なメソッドをサポートするため)。 したがって、これはかなり「高価」であり、CLR の変更が必要になると予想される他の機能と組み合わせて実行する価値がある可能性があります。

代替

なし。

未解決の質問

  • 上記の提案全体を通して、未解決の質問が提示されています。
  • 未解決の質問の一覧については、https://github.com/dotnet/csharplang/issues/406 も参照してください。
  • 詳細な仕様では、呼び出す正確なメソッドを選択するために実行時に使用される解決メカニズムを記述する必要があります。
  • 新しいコンパイラによって生成され、古いコンパイラによって使用されるメタデータの相互作用を詳細に解決する必要があります。 たとえば、使用するメタデータ表現によって、インターフェイスに既定の実装が追加され、古いコンパイラでコンパイルされたときにそのインターフェイスを実装する既存のクラスが壊れることがないようにする必要があります。 これは、使用できるメタデータ表現に影響する可能性があります。
  • 設計では、他の言語と他の言語の既存のコンパイラとの相互運用を検討する必要があります。

解決済みの質問

抽象オーバーライド

以前のドラフト仕様には、継承されたメソッドを「再抽象化」する機能が含まれていました。

interface IA
{
    void M();
}
interface IB : IA
{
    override void M() { }
}
interface IC : IB
{
    override void M(); // make it abstract again
}

2017 年 3 月 20 日のメモには、これを許可しないことに決めたことが記されていました。 ただし、少なくとも 2 つのユース ケースがあります。

  1. この機能の一部のユーザーが相互運用することを望んでいる Java API は、この機能に依存しています。
  2. これからのトレイトの利点があるプログラミング。 再抽象化は、「特性」言語機能 (https://en.wikipedia.org/wiki/Trait_(computer_programming)) の要素の 1 つです。 クラスでは、次の操作が許可されます。
public abstract class Base
{
    public abstract void M();
}
public abstract class A : Base
{
    public override void M() { }
}
public abstract class B : A
{
    public override abstract void M(); // reabstract Base.M
}

残念ながら、このコードは許可されない限り、インターフェイス (特性) のセットとしてリファクタリングすることはできません。 Jared principle of greed によって、許可される必要があります。

終了した問題: 再抽象化を許可する必要がありますか? [はい] 私のメモは間違っていました。 LDM のメモには、インターフェイスでの再抽象化が許可されていると記載されています。 クラスには所属していません。

仮想修飾子と sealed 修飾子

Aleksey Tsingauz から:

一部の修飾子を許可しない理由がない限り、インターフェイス メンバーに明示的に記述された修飾子を許可することにしました。 これにより、仮想修飾子に関する興味深い疑問が生じます。 既定の実装のメンバーでは必須でしょうか?

次のように言えるでしょう。

  • 実装がなく、仮想もシールも指定されていない場合、メンバーは抽象であると想定します。
  • 実装があり、抽象もシールも指定されていない場合、メンバーは仮想であると想定します。
  • sealed 修飾子は、メソッドを仮想にも抽象にもできないようにするために必要です。

または、仮想メンバーに仮想修飾子が必要であると言う場合があります。 つまり、実装が仮想修飾子で明示的にマークされていないメンバーがある場合、そのメンバーは仮想でも抽象でもありません。 この方法では、メソッドをクラスからインターフェイスに移動すると、エクスペリエンスが向上する可能性があります。

  • 抽象メソッドは抽象のままです。
  • 仮想メソッドは仮想のままです。
  • 修飾子のないメソッドは、仮想でも抽象でもありません。
  • sealed 修飾子は、オーバーライドではないメソッドに適用できません。

どのように思われたでしょうか。

終了した問題: 具体的なメソッド (実装あり) を暗黙的に virtualする必要がありますか? [はい]

決定事項: LDM 2017-04-05 で実行済み:

  1. 非仮想は、sealed または private を使用して明示的に表現する必要があります。
  2. sealed は、本文を持つインターフェイス インスタンス メンバーを非仮想にするためのキーワードです。
  3. インターフェイス内のすべての修飾子を許可する必要があります
  4. インターフェイス メンバーの既定のアクセシビリティは、入れ子になった型を含めてパブリックです
  5. インターフェイス内のプライベート関数メンバーは暗黙的にシールされ、sealed は許可されません。
  6. プライベート クラス (インターフェイス内) は許可されており、シールできます。これは、シールのクラスの意味でシールされることを意味します。
  7. 良い提案がない限り、インターフェースまたはそのメンバーにおいて部分的な実装は依然として許可されていません。

バイナリの互換性 1

ライブラリが既定の実装を提供する場合

interface I1
{
    void M() { Impl1 }
}
interface I2 : I1
{
}
class C : I2
{
}

C での I1.M の実装が I1.M であることを理解しています。 I2 を含むアセンブリが次のように変更され、再コンパイルされた場合

interface I2 : I1
{
    override void M() { Impl2 }
}

ただし、C は再コンパイルされません。 プログラムの実行時はどうなりますか? (C as I1).M() の呼び出し

  1. I1.M を実行
  2. I2.M を実行
  3. 何らかのランタイムエラーを発生させます

決定事項: 2017-04-11 に作成: 実行時に明確かつ最も具体的なオーバーライドである I2.M を実行します。

イベント アクセサー (クローズ)

終了した問題: イベントを「部分的に」上書きできますか?

この場合を考えてみましょう。

public interface I1
{
    event T e1;
}
public interface I2 : I1
{
    override event T
    {
        add { }
        // error: "remove" accessor missing
    }
}

クラスの場合と同様に、イベント宣言の構文では 1 つのアクセサーのみが許可されず、両方 (またはどちらも指定しない) が提供されなければならないため、イベントのこの「部分的な」実装は許可されません。 構文内の abstract remove アクセサーを、本文がないことによって暗黙的に抽象化できるようにすることで、同じことを実現できます。

public interface I1
{
    event T e1;
}
public interface I2 : I1
{
    override event T
    {
        add { }
        remove; // implicitly abstract
    }
}

これは新しい (提案された) 構文であることに注意してください。 現在の文法では、イベント アクセサーには必須の本文があります。

終了した問題: インターフェイスとプロパティ アクセサーのメソッドが本文の省略によって (暗黙的に) 抽象になるのと同様に、イベント アクセサーは本文の省略によって (暗黙的に) 抽象化できますか。

決定事項: (2017-04-18) いいえ。イベント宣言には、具体的なアクセサーの両方が必要です(またはどちらも不要です)。

クラス内の再抽象化(クローズド)

終了した問題: これが許可されていることを確認する必要があります (それ以外の場合は、既定の実装を追加すると破壊的変更になります)。

interface I1
{
    void M() { }
}
abstract class C : I1
{
    public abstract void M(); // implement I1.M with an abstract method in C
}

決定: (2017-04-18) はい。インターフェイス メンバー宣言に本文を追加すると C が壊れることはありません。

封印されたオーバーライド (クローズ)

前の質問では、インターフェイス内の sealedoverride 修飾子を適用できることを暗黙的に想定しています。 これはドラフト仕様と矛盾しています。 オーバーライドを封じることを許可しますか? シールのソースとバイナリの互換性の影響を考慮する必要があります。

終了した問題: オーバーライドの封鎖を許可しますか?

決定: (2017-04-18) インターフェイスでのオーバーライドの sealed を許可しないようにしましょうという結論に達しました。 インターフェイス メンバーで sealed を使用する唯一の方法は、最初の宣言で非仮想にすることです。

ダイアモンド継承とクラス (クローズド)

提案の下書きでは、ダイヤモンド継承シナリオでのインターフェイスオーバーライドよりもクラスオーバーライドが優先されます。

すべてのインターフェイスとクラスには、その型または直接および間接インターフェイスに表示されるオーバーライドのうち、すべてのインターフェイス メソッドに対して最も具体的なオーバーライドが必要です。 最も具体的なオーバーライドは、他のすべてのオーバーライドよりも具体的な一意のオーバーライドです。 オーバーライドがない場合、メソッド自体は最も具体的なオーバーライドと見なされます。

M1 が、T1 の型で宣言され、M2T2 の型で宣言された場合は、1 つのオーバーライドは、他のオーバーライドよりもM2より具体的M1であると見なされます。

  1. T1 には、その直接または間接インターフェイスの中に T2 を含みます、または
  2. T2 はインターフェイス型ですが、T1 はインターフェイス型ではありません。

このシナリオは次のようになります。

interface IA
{
    void M();
}
interface IB : IA
{
    override void M() { WriteLine("IB"); }
}
class Base : IA
{
    void IA.M() { WriteLine("Base"); }
}
class Derived : Base, IB // allowed?
{
    static void Main()
    {
        IA a = new Derived();
        a.M();           // what does it do?
    }
}

この動作を確認する (またはそれ以外の場合は決定する) 必要があります

終了した問題: 混合クラスとインターフェースに適用される最も具体的なオーバーライド について、上記のドラフト仕様を確認してください (クラスはインターフェースよりも優先されます)。 以下を参照してください。https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes

インターフェイス メソッドと構造体 (終了)

既定のインターフェイス メソッドと構造体の間には、いくつかの残念な相互作用があります。

interface IA
{
    public void M() { }
}
struct S : IA
{
}

インターフェイス メンバーは継承されないことに注意してください。

var s = default(S);
s.M(); // error: 'S' does not contain a member 'M'

その結果、クライアントはインターフェイス メソッドを呼び出すために構造体をボックス化する必要があります

IA s = default(S); // an S, boxed
s.M(); // ok

この方法でボックス化すると、struct 型の主な利点が損なわれます。 さらに、変更メソッドは構造体のボックス化されたコピーに対して動作するため、明らかな効果はありません。

interface IB
{
    public void Increment() { P += 1; }
    public int P { get; set; }
}
struct T : IB
{
    public int P { get; set; } // auto-property
}

T t = default(T);
Console.WriteLine(t.P); // prints 0
(t as IB).Increment();
Console.WriteLine(t.P); // prints 0

終了した問題: これについて何ができるでしょうか:

  1. struct が既定の実装を継承することを禁止します。 すべてのインターフェイス メソッドは、struct で抽象として扱われます。 その後、より適切に動作させる方法を決めるのに時間がかかる場合があります。
  2. ボックス化を回避する何らかのコード生成戦略を思い付きます。 IB.Increment のようなメソッド内では、this の型は、IB に制約された型パラメーターに似ている可能性があります。 これに伴い、呼び出し元でのボックス化を回避するために、非抽象メソッドはインターフェイスから継承されます。 これにより、コンパイラと CLR の実装作業が大幅に増加する可能性があります。
  3. 気にせず、そのままイボとして放置しておきましょう。
  4. 他のアイデアはありますか?

の決定: それを気にせず、ただのいぼとして放置してください。 以下を参照してください。https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations

基本インターフェイスの呼び出し (終了)

この決定は C# 8 では実装されませんでした。 base(Interface).M() 構文は実装されていません。

ドラフト仕様では、Java にヒントを得た基本インターフェイス呼び出しの構文が提案されています: Interface.base.M(). 少なくとも最初のプロトタイプでは、構文を選択する必要があります。 私のお気に入りは base<Interface>.M() です。

終了した問題: 基本メンバー呼び出しの構文は何ですか?

決定: 構文は base(Interface).M() です。 以下を参照してください。https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation 名前付きのインターフェイスは基本インターフェイスである必要がありますが、直接の基本インターフェイスである必要はありません。

未解決の問題: 基底インターフェイスの呼び出しをクラス メンバーで許可する必要がありますか?

決定: はい。 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation

非公開のインターフェース メンバーをオーバーライド (クローズ)

インターフェイスでは、基本インターフェイスの非パブリック メンバーは、override 修飾子を使用してオーバーライドされます。 メンバーを含むインターフェイスに名前を付けた「明示的な」オーバーライドの場合、アクセス修飾子は省略されます。

終了した問題: インターフェイスに名前を付けない「暗黙的な」オーバーライドである場合、アクセス修飾子は一致する必要がありますか?

決定事項: 一般メンバーのみが暗黙的にオーバーライドでき、アクセスが一致する必要があります。 以下を参照してください。https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list

未解決の問題: アクセス修飾子は必須か、省略可能か、あるいは override void IB.M() {} のような明示的なオーバーライドでは省略されますか?

未解決の問題:overrideなどの明示的なオーバーライドで、void IB.M() {} は必須か、省略可能か、または省略されるか。

クラスに非パブリック インターフェイス メンバーを実装するにはどうすればよいですか? おそらく明示的に行う必要があるのでしょうか?

interface IA
{
    internal void MI();
    protected void MP();
}
class C : IA
{
    // are these implementations?  Decision: NO
    internal void MI() {}
    protected void MP() {}
}

終了した問題: クラスに非パブリック インターフェイス メンバーを実装するにはどうすればよいですか?

決定: パブリック インターフェイス以外のメンバーのみを明示的に実装できます。 以下を参照してください。https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list

決定: インターフェイス メンバーで override キーワードを使用できません。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member

バイナリ互換性 2 (終了)

各型が別々のアセンブリにある次のコードを考えてみましょう。

interface I1
{
    void M() { Impl1 }
}
interface I2 : I1
{
    override void M() { Impl2 }
}
interface I3 : I1
{
}
class C : I2, I3
{
}

C での I1.M の実装が I2.M であることを理解しています。 I3 を含むアセンブリが次のように変更され、再コンパイルされた場合

interface I3 : I1
{
    override void M() { Impl3 }
}

ただし、C は再コンパイルされません。 プログラムの実行時はどうなりますか? (C as I1).M() の呼び出し

  1. I1.M を実行
  2. I2.M を実行
  3. I3.M を実行
  4. 2 または 3 のいずれか、決定論的に
  5. 何らかのランタイム例外をスローする

決定事項: 例外 (5) をスローします。 以下を参照してください。https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#issues-in-default-interface-methods

インターフェイスで partial を許可しますか? (終了)

インターフェイスは抽象クラスと似た方法で使用できるため、インターフェイスを宣言すると便利です partial。 これは、ジェネレーターに直面したときに特に役立ちます。

提案: インターフェイスとインターフェイスのメンバーが partial宣言しない場合がある言語制限を削除します。

決定: はい。 以下を参照してください。https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface

Main インターフェイス内で? (終了)

未解決の問題: インターフェイス内の static Main メソッドは、プログラムのエントリ ポイントになる候補ですか?

決定: はい。 以下を参照してください。https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#main-in-an-interface

パブリックな非仮想メソッドをサポートする意図を確認する (終了)

インターフェイスで非仮想パブリック メソッドを許可する決定を確認 (または取り消す) することはできますか?

interface IA
{
    public sealed void M() { }
}

Semi-Closed の問題: (2017-04-18) 役に立つと思いますが、再検討します。 これは、メンタル モデルの妨害要素です。

決定: はい。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#confirm-that-we-support-public-non-virtual-methods

インターフェイスの override によって新しいメンバーが導入されますか? (終了)

オーバーライド宣言によって新しいメンバーが導入されるかどうかを確認する方法はいくつかあります。

interface IA
{
    void M(int x) { }
}
interface IB : IA
{
    override void M(int y) { } // 'override' not permitted
}
interface IC : IB
{
    static void M2()
    {
        M(y: 3); // permitted? Decision: No.
    }
    override void IB.M(int z) { } // permitted? What does it override? Decision: No.
}

未解決の問題: インターフェイスのオーバーライド宣言で新しいメンバーが導入されますか? (終了)

クラスでは、オーバーライドメソッドは、ある意味では「可視」です。 たとえば、そのパラメーターの名前は、オーバーライドされたメソッドのパラメーターの名前よりも優先されます。 最も具体的なオーバーライドが常に存在するため、インターフェイスでその動作を複製できる場合があります。 しかし、その動作を再現したいのでしょうか?

また、オーバーライドメソッドを「オーバーライド」することは可能ですか? [議論の余地あり]

決定: インターフェイス メンバーで override キーワードを使用できません。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member

プライベート アクセサーを持つプロパティ (終了)

プライベート メンバーは仮想ではないため、仮想とプライベートの組み合わせは許可されません。 しかし、プライベート アクセサーを持つプロパティの場合はどうでしょうか?

interface IA
{
    public virtual int P
    {
        get => 3;
        private set { }
    }
}

これは許可されますか? setアクセサはここvirtualですか、それとも違いますか? アクセス可能な場所でオーバーライドできますか? 次のコードは、get アクセサーのみを暗黙的に実装しますか?

class C : IA
{
    public int P
    {
        get => 4;
        set { }
    }
}

次のものは、IA.P.set が仮想でなくアクセスできないためにエラーであると考えられますか?

class C : IA
{
    int IA.P
    {
        get => 4;
        set { } // Decision: Not valid
    }
}

決定: 最初の例は有効に見えますが、最後の例は有効ではありません。 これは、C# で既に動作している方法と同様に解決されます。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#properties-with-a-private-accessor

基本インターフェイス呼び出し、ラウンド 2 (終了)

これは C# 8 では実装されませんでした。

基本呼び出しの処理方法に関する以前の「解決策」では、実際には十分な表現力が得られませんでした。 C# と CLR では、Java とは異なり、メソッド宣言を含むインターフェイスと、呼び出す実装の場所の両方を指定する必要があります。

インターフェイス内の基本呼び出しには次の構文を提案します。 私はこれに惚れ込んでいるわけではありませんが、これはどんな構文でも表現できなければならないことを示しています。

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I4 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>(I1).M(); // calls I3's implementation of I1.M
        base<I4>(I1).M(); // calls I4's implementation of I1.M
    }
    void I2.M()
    {
        base<I3>(I2).M(); // calls I3's implementation of I2.M
        base<I4>(I2).M(); // calls I4's implementation of I2.M
    }
}

曖昧さがなければ、もっと簡単に記述することができます

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I4 : I1 { void I1.M() { } }
interface I5 : I3, I4
{
    void I1.M()
    {
        base<I3>.M(); // calls I3's implementation of I1.M
        base<I4>.M(); // calls I4's implementation of I1.M
    }
}

または

interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base(I1).M(); // calls I3's implementation of I1.M
    }
    void I2.M()
    {
        base(I2).M(); // calls I3's implementation of I2.M
    }
}

または

interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I5 : I3
{
    void I1.M()
    {
        base.M(); // calls I3's implementation of I1.M
    }
}

決定: base(N.I1<T>).M(s) を決定し、呼び出しバインディングがある場合は、後でここで問題が発生する可能性があることを認めます。https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md#default-interface-implementations

構造体が既定のメソッドを実装していないという警告ですか? (終了)

@vancem は、値型宣言がインターフェイス メソッドの実装をインターフェイスから継承する場合でも、そのインターフェイス メソッドをオーバーライドできない場合は警告を生成することを真剣に検討する必要があると主張しています。 ボックス化の原因となり、制約された呼び出しを台無しにするからです。

決定: これはアナライザーに適しているようです。 この警告は、既定のインターフェース メソッドが呼び出されず、ボックス化も発生しない場合でも発生するため、ノイズとなる可能性があるようです。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#warning-for-struct-not-implementing-default-method

インターフェイスの静的コンストラクター (終了)

インターフェイスの静的コンストラクターはいつ実行されますか? 現在の CLI ドラフトでは、最初の静的メソッドまたはフィールドにアクセスしたときに発生することが提案されています。 どちらも存在しない場合は、実行されない可能性がありますか?

[2018-10-09 CLR チームは、「Valuetypes の処理をミラーリングする (各インスタンス メソッドへのアクセスに関する cctor チェック)" を提案します」]

決定: 静的コンストラクターが beforefieldinit dえなかった場合は、インスタンス メソッドのエントリ時にも実行されます。この場合、静的コンストラクターは最初の静的フィールドにアクセスする前に実行されます。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#when-are-interface-static-constructors-run

デザインに関する会議

2017-03-08 LDM 会議メモ2017-03-21 LDM 会議メモ2017-03-23「デフォルトインターフェイス メソッドの CLR の挙動」に関する会議 2017-04-05 LDM 会議メモ2017-04-11 LDM 会議メモ2017-04-18 LDM 会議メモ2017-04-19 LDM 会議メモ2017-05-17 LDM 会議メモ2017-05-31 LDM 会議メモ2017-06-14 LDM 会議メモ2018-10-17 LDM 会議メモ2018-11-14 LDM 会議メモ