C#インターフェイスメソッドが抽象または仮想として宣言されていないのはなぜですか?


108

インターフェイスのC#メソッドは、virtualキーワードを使用せずに宣言され、overrideキーワードを使用せずに派生クラスでオーバーライドされます。

これには理由がありますか?私はそれが単に言語の便宜であると思います、そして明らかにCLRはカバーの下でこれを処理する方法を知っています(メソッドはデフォルトでは仮想ではありません)が、他の技術的な理由はありますか?

以下は、派生クラスが生成するILです。

class Example : IDisposable {
    public void Dispose() { }
}

.method public hidebysig newslot virtual final 
        instance void  Dispose() cil managed
{
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Example::Dispose

メソッドがvirtual finalILで宣言されていることに注意してください。

回答:


145

インターフェイスの場合、の追加abstract、またはpublicキーワードでさえ冗長になるため、それらを省略します。

interface MyInterface {
  void Method();
}

CILでは、メソッドはとマークされvirtualていabstractます。

(Javaではインターフェイスメンバーを宣言できることに注意してくださいpublic abstract)。

実装クラスには、いくつかのオプションがあります。

オーバーライド不可:C#では、クラスはメソッドをとして宣言しませんvirtual。つまり、派生クラスではオーバーライドできません(非表示のみ)。CILでは、インターフェイスの種類に関する多態性をサポートする必要があるため、メソッドは依然として仮想です(ただし、シールされています)。

class MyClass : MyInterface {
  public void Method() {}
}

オーバーライド可能:C#とCILの両方でメソッドはvirtualです。ポリモーフィックディスパッチに参加し、オーバーライドできます。

class MyClass : MyInterface {
  public virtual void Method() {}
}

明示的:これは、クラスがインターフェースを実装する方法ですが、クラス自体のパブリックインターフェースにインターフェースメソッドを提供しません。CILでは、メソッドはprivate(!)になりますが、対応するインターフェイスタイプへの参照から、クラスの外部から呼び出すことができます。明示的な実装もオーバーライドできません。これは.override、プライベートメソッドを、実装している対応するインターフェイスメソッドにリンクするCILディレクティブ()があるために可能です。

[C#]

class MyClass : MyInterface {
  void MyInterface.Method() {}
}

[CIL]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
  .override MyInterface::Method
}

VB.NETでは、実装クラスのインターフェイスメソッド名にエイリアスを設定することもできます。

[VB.NET]

Public Class MyClass
  Implements MyInterface
  Public Sub AliasedMethod() Implements MyInterface.Method
  End Sub
End Class

[CIL]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
  .override MyInterface::Method
}

今、この奇妙なケースを考えてみましょう:

interface MyInterface {
  void Method();
}
class Base {
  public void Method();
}
class Derived : Base, MyInterface { }

BaseおよびDerivedが同じアセンブリで宣言されている場合、インターフェイスを実装していなくBase::Methodても、コンパイラーは仮想化して(CILで)シールBaseします。

場合BaseDerived異なるアセンブリにあるコンパイルする際、Derivedアセンブリ、それはにメンバーをご紹介しますので、コンパイラは、他のアセンブリを変更しないDerivedために、明示的な実装されることをMyInterface::Methodそれはちょうどへの呼び出しを委任しますBase::Method

ご覧のとおり、コンパイラがフープを通過する必要がある場合でも、すべてのインターフェイスメソッドの実装はポリモーフィックな動作をサポートする必要があるため、CILで仮想としてマークする必要があります。


73

CSharp 3rd Editionを介してCLRからJeffrey Ritcherを引用

CLRでは、インターフェイスメソッドを仮想としてマークする必要があります。ソースコードでメソッドを明示的に仮想としてマークしない場合、コンパイラはメソッドを仮想およびシール済みとしてマークします。これにより、派生クラスがインターフェイスメソッドをオーバーライドするのを防ぎます。メソッドを明示的に仮想としてマークすると、コンパイラーはメソッドを仮想としてマークします(そして、シールを解除します)。これにより、派生クラスでインターフェイスメソッドをオーバーライドできます。インターフェイスメソッドがシールされている場合、派生クラスはメソッドをオーバーライドできません。ただし、派生クラスは同じインターフェイスを再継承でき、インターフェイスのメソッドに独自の実装を提供できます。


25
引用は、インターフェースメソッドの実装が仮想としてマークされる必要がある理由を述べていません。そうそれは、インターフェイスタイプに関して多型だからである必要のスロットを仮想仮想メソッドディスパッチを可能にするためにテーブル。
ジョルダン2010

1
インターフェイスメソッドを明示的に仮想としてマークすることはできず、エラー「エラーCS0106:修飾子 'virtual'はこのアイテムでは無効です」が表示されます。v2.0.50727(私のPCで最も古いバージョン)を使用してテストされています。
ccppjava 2013

3
@ccppjava以下のJoradoのコメントから、インターフェイスを実装しているクラスメンバーに仮想をマークして、サブクラスがクラスをオーバーライドできるようにします。
クリストファースティーブンソン

13

はい、インターフェースの実装方法はランタイムに関する限り仮想です。これは実装の詳細であり、インターフェースを機能させます。仮想メソッドは、クラスのvテーブルのスロットを取得します。各スロットには、仮想メソッドの1つへのポインターがあります。オブジェクトをインターフェイス型にキャストすると、インターフェイスメソッドを実装するテーブルのセクションへのポインターが生成されます。インターフェイス参照を使用するクライアントコードは、インターフェイスポインタなどからのオフセット0にある最初のインターフェイスメソッドポインタを参照します。

私の元の答えで過小評価していたのは、最終的な属性の重要性です。派生クラスが仮想メソッドをオーバーライドするのを防ぎます。派生クラスはインターフェイスを再実装する必要があります。実装メソッドは基本クラスメソッドをシャドウします。これは、実装方法が仮想ではないというC#言語コントラクトを実装するのに十分です。

ExampleクラスでDispose()メソッドを仮想として宣言すると、最終的な属性が削除されます。派生クラスがそれをオーバーライドできるようになりました。


4

他のほとんどのコンパイル済みコード環境では、インターフェースはvtables-メソッド本体へのポインターのリストとして実装されます。通常、複数のインターフェースを実装するクラスは、内部コンパイラによって生成されたメタデータのどこかに、インターフェースvtableのリストを持っています(インターフェースごとに1つのvtableです(そのため、メソッドの順序は保持されます)。これは通常、COMインターフェイスも実装されている方法です。

ただし、.NETでは、インターフェイスはクラスごとに異なるvtableとして実装されていません。インターフェイスメソッドは、すべてのインターフェイスがその一部であるグローバルインターフェイスメソッドテーブルを通じてインデックスが作成されます。したがって、そのメソッドがインターフェイスメソッドを実装するために、メソッドをvirtualと宣言する必要はありません。グローバルインターフェイスメソッドテーブルは、クラスのメソッドのコードアドレスを直接指すだけです。

他の言語では、CLR以外のプラットフォームであっても、インターフェイスを実装するために仮想メソッドを宣言する必要はありません。Win32上のDelphi言語はその一例です。


0

それらは仮想的ではありません(基盤となる実装に関して(密封された仮想)としてではないとしても、私たちがそれらをどのように考えるかに関して)-ここで他の回答を読んで、自分で何かを学ぶのは良いことです:-)

それらは何もオーバーライドしません-インターフェースに実装はありません。

インターフェースが行うすべてのことは、クラスが従わなければならない「契約」を提供することです-必要に応じてパターン。これにより、呼び出し元は、特定のクラスを見たことがない場合でも、オブジェクトを呼び出す方法を知ることができます。

次に、コントラクトの範囲内で、仮想または「非仮想」(結局のところ密封された仮想)のように、インターフェイスメソッドを実装するのはクラス次第です。


このスレッドの誰もがインターフェイスの目的を知っています。質問は非常に具体的です。生成されたIL 、インターフェースメソッドでは仮想であり、非インターフェースメソッドでは仮想ではありません。
Rex M

5
ええ、質問が編集された後に答えを批判するのは本当に簡単ですよね?
Jason Williams、

0

インターフェースはクラスよりも抽象的な概念であり、インターフェースを実装するクラスを宣言するとき、「クラスにはインターフェースからのこれらの特定のメソッドが必要であり、staticvirtualnon virtualオーバーライドのいずれであるかは関係ありません。IDとタイプパラメータが同じである限り」

Object Pascal( "Delphi")やObjective-C(Mac)などのインターフェイスをサポートする他の言語でも、インターフェイスメソッドを仮想ではなく仮想としてマークする必要はありません。

しかし、あなたは正しいかもしれません。特定のインターフェースを実装するクラスのメソッドを制限したい場合は、インターフェースに特定の「仮想」/「オーバーライド」属性を持たせることをお勧めします。ただし、これは、両方のインターフェースに「非仮想」、「dontcareifvirtualornot」というキーワードがあることも意味します。

クラスメソッドが「@virtual」または「@override」を使用してメソッドが仮想であることを確認する必要がある場合、Javaでも同様のものが表示されるため、質問を理解しました。


@overrideが実際にコードの動作を変更したり、結果のバイトコードを変更したりすることはありません。それが行うことは、そのように装飾されたメソッドがオーバーライドであることを意図していることをコンパイラーに知らせ、コンパイラーがいくつかの健全性チェックを行えるようにします。C#の動作は異なります。override言語自体の第一級のキーワードです。
Robert Harvey
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.