Mengapa metode antarmuka C # tidak dinyatakan abstrak atau virtual?

108

Metode C # di antarmuka dideklarasikan tanpa menggunakan virtualkata kunci, dan diganti di kelas turunan tanpa menggunakan overridekata kunci.

Apakah ada alasan untuk ini? Saya berasumsi bahwa ini hanya kenyamanan bahasa, dan jelas CLR tahu bagaimana menangani ini di bawah selimut (metode tidak virtual secara default), tetapi apakah ada alasan teknis lainnya?

Berikut adalah IL yang dihasilkan kelas turunan:

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

Perhatikan bahwa metode tersebut dideklarasikan virtual finaldi IL.

Robert Harvey
sumber

Jawaban:

145

Untuk antarmuka, penambahan abstract, atau bahkan publickata kunci akan menjadi mubazir, jadi Anda menghilangkannya:

interface MyInterface {
  void Method();
}

Di CIL, metode ini ditandai virtualdan abstract.

(Perhatikan bahwa Java memungkinkan anggota antarmuka untuk dideklarasikan public abstract).

Untuk kelas pelaksana, ada beberapa opsi:

Non-overridable : Dalam C # kelas tidak mendeklarasikan metode sebagai virtual. Artinya, kelas tersebut tidak dapat diganti dalam kelas turunan (hanya tersembunyi). Dalam CIL, metode ini masih virtual (tetapi disegel) karena harus mendukung polimorfisme terkait tipe antarmuka.

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

Dapat diganti : Baik di C # dan di CIL metodenya adalah virtual. Ini berpartisipasi dalam pengiriman polimorfik dan dapat diganti.

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

Eksplisit : Ini adalah cara kelas untuk mengimplementasikan antarmuka tetapi tidak menyediakan metode antarmuka dalam antarmuka publik dari kelas itu sendiri. Dalam CIL, metode ini akan menjadi private(!) Tetapi masih dapat dipanggil dari luar kelas dari referensi ke tipe antarmuka yang sesuai. Implementasi eksplisit juga tidak bisa diganti. Ini dimungkinkan karena ada direktif CIL ( .override) yang akan menghubungkan metode privat ke metode antarmuka terkait yang diimplementasikannya.

[C #]

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

[CIL]

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

Di VB.NET, Anda bahkan dapat membuat alias nama metode antarmuka di kelas implementasi.

[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
}

Sekarang, pertimbangkan kasus aneh ini:

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

Jika Basedan Deriveddideklarasikan dalam perakitan yang sama, kompilator akan membuat Base::Methodvirtual dan tertutup (dalam CIL), meskipun Baseantarmuka tidak mengimplementasikan.

Jika Basedan Derivedberada dalam rakitan yang berbeda, saat mengompilasi Derivedrakitan, kompilator tidak akan mengubah rakitan lain, jadi ia akan memperkenalkan anggota di dalamnya Derivedyang akan menjadi implementasi eksplisit MyInterface::Methodyang hanya akan mendelegasikan panggilan ke Base::Method.

Jadi Anda lihat, setiap implementasi metode antarmuka harus mendukung perilaku polimorfik, dan dengan demikian harus ditandai virtual pada CIL, bahkan jika kompilator harus melalui rintangan untuk melakukannya.

Jordão
sumber
73

Mengutip Jeffrey Ritcher dari CLR via CSharp 3rd Edition di sini

CLR mengharuskan metode antarmuka ditandai sebagai virtual. Jika Anda tidak secara eksplisit menandai metode sebagai virtual dalam kode sumber Anda, kompilator menandai metode sebagai virtual dan tersegel; hal ini mencegah kelas turunan menimpa metode antarmuka. Jika Anda secara eksplisit menandai metode sebagai virtual, compiler menandai metode tersebut sebagai virtual (dan membiarkannya terbuka); ini memungkinkan kelas turunan untuk mengganti metode antarmuka. Jika metode antarmuka disegel, kelas turunan tidak dapat menggantikan metode tersebut. Namun, kelas turunan dapat mewarisi kembali antarmuka yang sama dan dapat menyediakan implementasinya sendiri untuk metode antarmuka.

Usman Khan
sumber
25
Kutipan tidak menjelaskan mengapa penerapan metode antarmuka harus ditandai virtual. Itu karena polimorfik berkaitan dengan jenis antarmuka, sehingga membutuhkan slot pada tabel virtual untuk memungkinkan pengiriman metode virtual.
Jordão
1
Saya tidak diizinkan untuk secara eksplisit menandai metode antarmuka sebagai virtual, dan mendapatkan kesalahan "kesalahan CS0106: Pengubah 'virtual' tidak valid untuk item ini". Diuji menggunakan v2.0.50727 (versi tertua di PC saya).
ccppjava
3
@ccppjava Dari komentar Jorado di bawah, Anda menandai anggota kelas yang mengimplementasikan antarmuka virtual untuk memungkinkan subclass menimpa kelas.
Christopher Stevenson
13

Ya, metode implementasi antarmuka adalah virtual sejauh menyangkut waktu proses. Ini adalah detail implementasi, itu membuat antarmuka berfungsi. Metode virtual mendapatkan slot di kelas 'v-table, setiap slot memiliki penunjuk ke salah satu metode virtual. Mentransmisikan objek ke jenis antarmuka menghasilkan penunjuk ke bagian tabel yang mengimplementasikan metode antarmuka. Kode klien yang menggunakan referensi antarmuka sekarang melihat penunjuk metode antarmuka pertama di offset 0 dari penunjuk antarmuka, dan sebagainya.

Apa yang kurang saya hargai dalam jawaban asli saya adalah pentingnya atribut terakhir . Ini mencegah kelas turunan menimpa metode virtual. Kelas turunan harus mengimplementasikan ulang antarmuka, metode implementasi membayangi metode kelas dasar. Yang cukup untuk mengimplementasikan kontrak bahasa C # yang mengatakan bahwa metode implementasi tidak virtual.

Jika Anda mendeklarasikan metode Dispose () di kelas Contoh sebagai virtual, Anda akan melihat atribut terakhir dihapus. Sekarang mengizinkan kelas turunan untuk menimpanya.

Hans Passant
sumber
4

Di sebagian besar lingkungan kode terkompilasi lainnya, antarmuka diimplementasikan sebagai vtable - daftar pointer ke badan metode. Biasanya sebuah kelas yang mengimplementasikan banyak antarmuka akan memiliki suatu tempat di kompiler internalnya yang menghasilkan metadata daftar vtables antarmuka, satu vtable per antarmuka (sehingga urutan metode dipertahankan). Ini adalah bagaimana antarmuka COM biasanya diimplementasikan juga.

Dalam .NET, antarmuka tidak diimplementasikan sebagai vtabel berbeda untuk setiap kelas. Metode antarmuka diindeks melalui tabel metode antarmuka global yang semua antarmuka menjadi bagiannya. Oleh karena itu, tidak perlu mendeklarasikan metode virtual agar metode tersebut dapat mengimplementasikan metode antarmuka - tabel metode antarmuka global dapat langsung menunjuk ke alamat kode metode kelas.

Mendeklarasikan metode virtual untuk mengimplementasikan antarmuka juga tidak diperlukan dalam bahasa lain, bahkan dalam platform non-CLR. Bahasa Delphi di Win32 adalah salah satu contohnya.

dthorpe.dll
sumber
0

Mereka tidak virtual (dalam hal bagaimana kami memikirkannya, jika tidak dalam hal implementasi yang mendasarinya sebagai (virtual tersegel) - bagus untuk membaca jawaban lain di sini dan belajar sesuatu sendiri :-)

Mereka tidak menimpa apa pun - tidak ada implementasi di antarmuka.

Semua antarmuka menyediakan "kontrak" yang harus dipatuhi kelas - pola, jika Anda suka, sehingga pemanggil tahu cara memanggil objek meskipun mereka belum pernah melihat kelas tertentu sebelumnya.

Terserah kelas untuk kemudian mengimplementasikan metode antarmuka sebagaimana mestinya, dalam batasan kontrak - virtual atau "non-virtual" (ternyata virtual tertutup).

Jason Williams
sumber
semua orang di utas ini tahu untuk apa antarmuka itu. Pertanyaannya sangat spesifik - IL yang dihasilkan adalah virtual untuk metode antarmuka dan bukan virtual untuk metode non-antarmuka.
Rex M
5
Ya, sangat mudah untuk mengkritik jawaban setelah pertanyaan diedit, bukan?
Jason Williams
0

Antarmuka adalah konsep yang lebih abstrak daripada kelas, ketika Anda mendeklarasikan kelas yang mengimplementasikan antarmuka, Anda hanya mengatakan "kelas harus memiliki metode khusus ini dari antarmuka, dan tidak masalah apakah statis , virtual , non virtual , diganti , sebagai asalkan memiliki ID yang sama dan parameter jenis yang sama ".

Bahasa lain yang mendukung antarmuka seperti Object Pascal ("Delphi") dan Objective-C (Mac) tidak memerlukan metode antarmuka untuk ditandai virtual dan bukan virtual, baik.

Namun, Anda mungkin benar, menurut saya mungkin ide yang baik untuk memiliki atribut "virtual" / "override" tertentu di antarmuka, jika Anda ingin membatasi metode kelas yang mengimplementasikan antarmuka tertentu. Namun, itu juga berarti memiliki kata kunci "nonvirtual", "dontcareifvirtualornot", untuk kedua antarmuka.

Saya memahami pertanyaan Anda, karena saya melihat sesuatu yang serupa di Java, ketika metode kelas harus menggunakan "@virtual" atau "@override" untuk memastikan bahwa suatu metode dimaksudkan untuk menjadi virtual.

umlcat
sumber
@override sebenarnya tidak mengubah perilaku kode atau mengubah kode byte yang dihasilkan. Apa yang dilakukannya adalah memberi sinyal kepada kompilator bahwa metode yang didekorasi dengan demikian dimaksudkan sebagai timpaan, yang memungkinkan kompilator untuk melakukan beberapa pemeriksaan kewarasan. C # bekerja secara berbeda; overrideadalah kata kunci kelas satu dalam bahasa itu sendiri.
Robert Harvey