Bagikan melalui


19 Antarmuka

19.1 Umum

Antarmuka mendefinisikan kontrak. Kelas atau struktur yang mengimplementasikan antarmuka harus mematuhi kontraknya. Antarmuka dapat mewarisi dari beberapa antarmuka dasar, dan kelas atau struktur dapat mengimplementasikan beberapa antarmuka.

Antarmuka mungkin berisi berbagai jenis anggota, seperti yang dijelaskan dalam §19.4. Antarmuka itu sendiri dapat memberikan implementasi untuk beberapa atau semua anggota fungsi yang dinyatakannya. Anggota yang antarmukanya tidak menyediakan implementasi abstrak. Implementasinya harus disediakan oleh kelas atau struktur yang mengimplementasikan antarmuka, atau antarmuka turunan yang memberikan definisi penimpaan.

Catatan: Secara historis, menambahkan anggota fungsi baru ke antarmuka berdampak pada semua konsumen yang ada dari jenis antarmuka tersebut; itu adalah perubahan yang melanggar. Penambahan implementasi anggota fungsi antarmuka memungkinkan pengembang untuk meningkatkan antarmuka sambil tetap memungkinkan pelaksana apa pun untuk mengambil alih implementasi tersebut. Pengguna antarmuka dapat menerima implementasi sebagai perubahan yang tidak melanggar; namun, jika persyaratannya berbeda, mereka dapat mengambil alih implementasi yang disediakan. catatan akhir

19.2 Deklarasi antarmuka

19.2.1 Umum

interface_declaration adalah type_declaration (§14,7) yang mendeklarasikan jenis antarmuka baru.

interface_declaration
    : attributes? interface_modifier* 'partial'? 'interface'
      identifier variant_type_parameter_list? interface_base?
      type_parameter_constraints_clause* interface_body ';'?
    ;

Interface_declaration terdiri dari sekumpulan atribut opsional (§23), diikuti oleh sekumpulan interface_modifieropsional (§19.2.2), diikuti oleh pengubah parsial opsional (§15.2.7), diikuti oleh kata kunci interface dan pengidentifikasi yang menamai antarmuka, diikuti dengan spesifikasi variant_type_parameter_list opsional (§19.2.3), diikuti dengan spesifikasi interface_base opsional (§19.2.4), diikuti dengan spesifikasi type_parameter_constraints_clauseopsional (§15.2.5), diikuti dengan interface_body (§19,3), secara opsional diikuti dengan titik koma.

Deklarasi antarmuka tidak boleh menyediakan type_parameter_constraints_clausekecuali juga menyediakan variant_type_parameter_list.

Deklarasi antarmuka yang menyediakan variant_type_parameter_list adalah deklarasi antarmuka generik. Selain itu, antarmuka apa pun yang ada di dalam deklarasi kelas generik atau deklarasi struct generik adalah deklarasi antarmuka generik, karena argumen tipe untuk tipe yang berisi harus disediakan untuk membuat tipe yang telah dibangun (§8.4).

19.2.2 Pengubah antarmuka

Interface_declaration dapat secara opsional menyertakan urutan pengubah antarmuka:

interface_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§24.2) hanya tersedia dalam kode tidak aman (§24).

Ini adalah kesalahan pada waktu kompilasi jika pengubah yang sama muncul beberapa kali dalam deklarasi antarmuka.

Pengubah new hanya diizinkan pada antarmuka yang ditentukan dalam kelas. Ini menentukan bahwa antarmuka menyembunyikan anggota yang diwariskan dengan nama yang sama, seperti yang dijelaskan dalam §15.3.5.

Pengubah public, protected, internal, dan private mengontrol aksesibilitas antarmuka. Tergantung pada konteks di mana deklarasi antarmuka terjadi, hanya beberapa pengubah ini yang mungkin diizinkan (§7.5.2). Ketika deklarasi jenis parsial (§15.2.7) menyertakan spesifikasi aksesibilitas (melalui publicpengubah , , protected, internaldan private ), aturan dalam §15.2.2 berlaku.

19.2.3 Daftar parameter jenis varian

19.2.3.1 Umum

Daftar parameter tipe varian hanya dapat terjadi pada tipe antarmuka dan delegasi. Perbedaan dari type_parameter_list biasa adalah variance_annotation opsional pada setiap parameter jenis.

variant_type_parameter_list
    : '<' variant_type_parameter (',' variant_type_parameter)* '>'
    ;

variant_type_parameter
    : attributes? variance_annotation? type_parameter
    ;

variance_annotation
    : 'in'
    | 'out'
    ;

Jika anotasi varians adalah out, parameter jenis disebut sebagai kovarian. Jika anotasi varians adalah in, parameter jenis dikatakan kontravarian. Jika tidak ada anotasi varians, parameter jenis dikatakan invarian.

Contoh: Dalam hal berikut:

interface C<out X, in Y, Z>
{
    X M(Y y);
    Z P { get; set; }
}

X kovarian, Y kontravarian dan Z invarian.

contoh akhir

Jika antarmuka generik dideklarasikan dalam beberapa bagian (§15.2.3), setiap deklarasi parsial harus menentukan varians yang sama untuk setiap parameter jenis.

19.2.3.2 Keamanan varians

Terjadinya anotasi varians dalam daftar parameter jenis membatasi tempat di mana jenis dapat terjadi dalam deklarasi jenis.

Jenis T tidak aman untuk output jika salah satu kondisi berikut terpenuhi:

  • T adalah parameter jenis kontravarian
  • T adalah jenis array dengan jenis elemen yang tidak aman untuk keluaran
  • T adalah antarmuka atau Sᵢ,... Aₑ tipe delegasi yang dibuat dari tipe generik S<Xᵢ, ... Xₑ> di mana untuk setidaknya satu Aᵢ, salah satu dari kondisi berikut terpenuhi:
    • Xᵢ kovarian atau invarian dan Aᵢ output tidak aman.
    • Xᵢ bersifat kontravarian atau invarian dan Aᵢ tidak aman untuk input.

Jenis T tidak aman untuk input jika salah satu hal berikut ini berlaku:

  • T adalah parameter tipe kovarian
  • T adalah jenis array dengan jenis elemen yang tidak aman untuk input
  • T adalah antarmuka atau S<Aᵢ,... Aₑ> tipe delegasi yang dibuat dari tipe generik S<Xᵢ, ... Xₑ> di mana untuk setidaknya satu Aᵢ, salah satu dari kondisi berikut terpenuhi:
    • Xᵢ kovarian atau invarian dan Aᵢ tidak aman input.
    • Xᵢ adalah kontravarian atau invarian dan Aᵢ tidak aman untuk keluaran.

Secara intuitif, jenis yang tidak aman untuk output dilarang dalam posisi output, dan jenis yang tidak aman untuk input dilarang dalam posisi input.

Jenis adalah output-safe jika tidak output-unsafe, dan input-safe jika tidak input-unsafe.

19.2.3.3 Konversi varians

Tujuan anotasi varians adalah untuk menyediakan konversi yang lebih fleksibel (tetapi masih aman untuk tipe) ke tipe antarmuka dan delegasi. Untuk ini, definisi implisit (§10,2) dan konversi eksplisit (§10,3) menggunakan gagasan konvertibilitas varians, yang didefinisikan sebagai berikut:

Jenis T<Aᵢ, ..., Aᵥ> dapat dikonversi varians ke jenis T<Bᵢ, ..., Bᵥ> jika T merupakan antarmuka atau jenis delegasi yang dideklarasikan dengan parameter jenis varian T<Xᵢ, ..., Xᵥ>, dan untuk setiap parameter jenis varian Xᵢ, salah satu dari kondisi berikut harus terpenuhi:

  • Xᵢ bersifat kovarian dan terdapat konversi referensi implisit atau identitas dari Aᵢ ke Bᵢ
  • Xᵢ adalah kontravarian dan terdapat referensi implisit atau konversi identitas dari Bᵢ ke Aᵢ.
  • Xᵢ invariant dan konversi identitas ada dari Aᵢ ke Bᵢ

19.2.4 Antarmuka dasar

Antarmuka dapat mewarisi dari nol atau lebih jenis antarmuka, yang disebut antarmuka dasar eksplisitdari antarmuka. Ketika antarmuka memiliki satu atau beberapa antarmuka dasar eksplisit, maka dalam deklarasi antarmuka tersebut, pengidentifikasi antarmuka diikuti oleh titik dua dan daftar jenis antarmuka dasar yang dipisahkan koma.

Antarmuka turunan dapat menyatakan anggota baru yang menyembunyikan anggota yang diwariskan (§7.7.2.3) yang dideklarasikan dalam antarmuka dasar atau secara eksplisit menerapkan anggota yang diwariskan (§19.6.2) yang dideklarasikan dalam antarmuka dasar.

interface_base
    : ':' interface_type_list
    ;

Antarmuka dasar eksplisit dapat dibangun jenis antarmuka (§8.4, §19.2). Antarmuka dasar tidak dapat menjadi parameter jenis sendiri, meskipun dapat melibatkan parameter jenis yang berada dalam cakupan.

Untuk jenis antarmuka yang dibangun, antarmuka dasar eksplisit dibentuk dengan mengambil deklarasi antarmuka dasar eksplisit pada deklarasi jenis generik, lalu mengganti, untuk setiap type_parameter dalam deklarasi antarmuka dasar, type_argument yang sesuai dari jenis yang dibangun.

Antarmuka dasar eksplisit dari sebuah antarmuka harus setidaknya seaksesibel antarmuka itu sendiri (§7.5.5).

Catatan: Misalnya, ini adalah kesalahan waktu kompilasi untuk menentukan antarmuka private atau internal di dalam interface_base dari antarmuka public. catatan akhir

Ini adalah kesalahan waktu kompilasi bagi antarmuka untuk secara langsung atau tidak langsung mewarisi dari dirinya sendiri.

Antarmuka dasarantarmuka adalah antarmuka dasar eksplisit dan antarmuka dasarnya. Dengan kata lain, sekumpulan antarmuka dasar adalah penutupan transitif yang lengkap dari antarmuka dasar eksplisit, antarmuka dasar eksplisit mereka, dan sebagainya. Antarmuka mewarisi semua anggota antarmuka dasarnya.

Contoh: Dalam kode berikut

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

antarmuka IComboBox dasar adalah IControl, ITextBox, dan IListBox. Dengan kata lain, IComboBox antarmuka di atas mewarisi anggota SetText dan SetItems serta Paint.

contoh akhir

Anggota yang diwarisi dari tipe generik yang telah ditentukan akan diwarisi setelah penggantian tipe. Artinya, setiap tipe konstituen dalam anggota memiliki parameter tipe dalam deklarasi kelas dasar yang digantikan oleh argumen tipe yang sesuai yang digunakan dalam spesifikasi class_base.

Contoh: Dalam kode berikut

interface IBase<T>
{
    T[] Combine(T a, T b);
}

interface IDerived : IBase<string[,]>
{
    // Inherited: string[][,] Combine(string[,] a, string[,] b);
}

antarmuka IDerived mewarisi metode Combine setelah parameter jenis T diganti dengan string[,].

contoh akhir

Kelas atau struktur yang mengimplementasikan antarmuka juga secara implisit mengimplementasikan semua antarmuka dasar antarmuka.

Penanganan antarmuka pada beberapa bagian dari deklarasi antarmuka parsial (§15.2.7) dibahas lebih lanjut dalam §15.2.4.3.

Setiap antarmuka dasar antarmuka harus aman output (§19.2.3.2).

19.3 Isi antarmuka

Interface_body antarmuka menentukan anggota antarmuka.

interface_body
    : '{' interface_member_declaration* '}'
    ;

19.4 Anggota antarmuka

19.4.1 Umum

Anggota antarmuka adalah anggota yang diwarisi dari antarmuka dasar dan anggota yang dideklarasikan oleh antarmuka itu sendiri.

interface_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | static_constructor_declaration
    | operator_declaration
    | type_declaration
    ;

Klausa ini menambah deskripsi anggota di kelas (§15,3) dengan batasan untuk antarmuka. Anggota Antarmuka dinyatakan menggunakan member_declarationdengan aturan tambahan berikut:

  • Finalizer_declaration tidak diperbolehkan.
  • Konstruktor instans, constructor_declaration, tidak diizinkan.
  • Semua anggota antarmuka secara implisit memiliki akses publik; namun, pengubah akses eksplisit (§7.5.2) diizinkan kecuali pada konstruktor statis (§15.12).
  • Pengubah abstract tersirat untuk anggota fungsi antarmuka tanpa badan; pengubah tersebut dapat diberikan secara eksplisit.
  • Anggota fungsi instans antarmuka yang deklarasinya menyertakan isi adalah anggota implisit virtual kecuali pengubah sealed atau private digunakan. Pengubah virtual dapat diberikan secara eksplisit.
  • Anggota private fungsi atau sealed antarmuka harus memiliki isi.
  • Anggota private fungsi tidak boleh memiliki pengubah sealed.
  • Antarmuka turunan dapat mengambil alih anggota abstrak atau virtual yang dideklarasikan dalam antarmuka dasar.
  • Anggota fungsi yang diimplementasikan secara eksplisit tidak boleh memiliki pengubah sealed.

Beberapa deklarasi, seperti constant_declaration (§15.4) tidak memiliki batasan dalam antarmuka.

Anggota antarmuka yang diwariskan secara khusus bukan bagian dari ruang deklarasi antarmuka. Dengan demikian, antarmuka diizinkan untuk mendeklarasikan anggota dengan nama atau tanda tangan yang sama dengan anggota yang diwariskan. Ketika ini terjadi, anggota antarmuka turunan dikatakan menyembunyikan anggota antarmuka dasar. Menyembunyikan anggota yang diwariskan tidak dianggap sebagai kesalahan, tetapi itu menghasilkan peringatan (§7.7.2.3).

Jika pengubah new disertakan dalam deklarasi yang tidak menyembunyikan anggota turunan, peringatan dikeluarkan sebagai akibatnya.

Catatan: Anggota di kelas object tidak, secara ketat berbicara, anggota antarmuka apa pun (§19.4). Namun, anggota di kelas object tersedia melalui pencarian anggota dalam jenis antarmuka apa pun (§12,5). catatan akhir

Kumpulan anggota antarmuka yang dideklarasikan dalam beberapa bagian (§15.2.7) adalah gabungan dari anggota yang dideklarasikan di setiap bagian. Badan semua bagian deklarasi antarmuka memiliki ruang deklarasi yang sama (§7,3), dan cakupan setiap anggota (§7,7) meluas ke badan semua bagian.

Contoh: Pertimbangkan antarmuka IA dengan implementasi untuk anggota M dan properti P. Jenis C penerapan tidak menyediakan implementasi untuk M atau P. Mereka harus diakses melalui referensi yang jenis waktu kompilasinya adalah antarmuka yang secara implisit dapat dikonversi ke IA atau IB. Anggota ini tidak ditemukan melalui pencarian anggota pada variabel jenis C.

interface IA
{
    public int P { get { return 10; } }
    public void M()
    {
        Console.WriteLine("IA.M");
    }
}

interface IB : IA
{
    public new int P { get { return 20; } }
    void IA.M()
    {
        Console.WriteLine("IB.M");
    }
}

class C : IB { }

class Test
{
    public static void Main()
    {
        C c = new C();
        ((IA)c).M();                               // cast needed
        Console.WriteLine($"IA.P = {((IA)c).P}");  // cast needed
        Console.WriteLine($"IB.P = {((IB)c).P}");  // cast needed
    }
}

Dalam antarmuka IA dan IB, anggota M dapat diakses langsung berdasarkan nama. Namun, dalam metode Main, kami tidak dapat menulis c.M() atau c.P, karena nama-nama tersebut tidak terlihat. Untuk menemukannya, diperlukan transmisi ke jenis antarmuka yang sesuai. Deklarasi dalam M menggunakan sintaks implementasi IB antarmuka eksplisit. Ini diperlukan untuk membuat metode tersebut mengambil alih metode di IA; pengubah override mungkin tidak diterapkan ke anggota fungsi. contoh akhir

19.4.2 Bidang antarmuka

Klausa ini menambah deskripsi bidang di kelas §15,5 untuk bidang yang dideklarasikan dalam antarmuka.

Bidang antarmuka dinyatakan menggunakan field_declaration(§15.5.1) dengan aturan tambahan berikut:

  • Ini adalah kesalahan waktu kompilasi bagi field_declaration untuk mendeklarasikan bidang instans.

Contoh: Program berikut berisi anggota statis dari berbagai jenis:

public interface IX
{
    public const int Constant = 100;
    protected static int field;

    static IX()
    {
        Console.WriteLine("static members initialized");
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
        field = 50;
        Console.WriteLine("static constructor has run");
    }
}

public class Test: IX
{
    public static void Main()
    {
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
    }
}

Output yang dihasilkan adalah

static members initialized
constant = 100, field = 0
static constructor has run
constant = 100, field = 50

contoh akhir

Lihat §19.4.8 untuk informasi mengenai alokasi dan inisialisasi bidang statis.

19.4.3 Metode antarmuka

Klausa ini menambah deskripsi metode di kelas §15.6 untuk metode yang dideklarasikan dalam antarmuka.

Metode antarmuka dinyatakan menggunakan method_declarations (§15.6)). Atribut, return_type, ref_return_type, pengidentifikasi, dan parameter_list deklarasi metode antarmuka memiliki arti yang sama dengan deklarasi metode di kelas. Metode antarmuka memiliki aturan tambahan berikut:

  • method_modifier tidak boleh menyertakan override.

  • Metode yang tubuhnya adalah titik koma (;) adalah abstract; pengubah abstract tidak diperlukan, tetapi diizinkan.

  • Deklarasi metode antarmuka yang memiliki badan blok atau badan ekspresi sebagai method_body adalah virtual; virtual pengubah tidak diperlukan, tetapi diizinkan.

  • Method_declaration tidak boleh memiliki type_parameter_constraints_clausekecuali juga memiliki type_parameter_list.

  • Daftar persyaratan untuk kombinasi pengubah yang valid yang dinyatakan untuk metode kelas diperluas, sebagai berikut:

    • Deklarasi statis yang tidak diekstern harus memiliki badan blok atau badan ekspresi sebagai method_body.
    • Deklarasi virtual yang tidak diekstern harus memiliki badan blok atau badan ekspresi sebagai method_body.
    • Deklarasi privat yang tidak diekstern harus memiliki badan blok atau badan ekspresi sebagai method_body.
    • Deklarasi tertutup yang tidak diekstern harus memiliki badan blok atau badan ekspresi sebagai method_body.
    • Deklarasi asinkron harus memiliki badan blok atau badan ekspresi sebagai method_body.
  • Semua jenis parameter metode antarmuka harus aman input (§19.2.3.2), dan jenis pengembalian harus void aman atau output.

  • Setiap jenis parameter output atau referensi juga harus aman output.

    Catatan: Parameter output diperlukan agar aman input karena pembatasan implementasi umum. catatan akhir

  • Setiap batasan jenis kelas, batasan jenis antarmuka, dan batasan parameter jenis pada parameter jenis apa pun dari metode harus aman input.

Aturan ini memastikan bahwa setiap penggunaan antarmuka yang kovarian atau kontravarian tetap typeafe.

Contoh:

interface I<out T>
{
    void M<U>() where U : T;     // Error
}

salah bentuk karena penggunaan T sebagai batasan parameter jenis pada U yang tidak aman untuk input.

Jika pembatasan ini tidak diberlakukan, maka akan mungkin untuk melanggar keamanan tipe dengan cara berikut:

interface I<out T>
{
    void M<U>() where U : T;
}
class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<D>() {...} 
}

...

I<B> b = new C();
b.M<E>();

Ini sebenarnya panggilan ke C.M<E>. Tetapi panggilan itu mengharuskan bahwa E berasal dari D, sehingga keamanan tipe akan dilanggar di sini.

contoh akhir

Catatan: Lihat §19.4.2 untuk contoh yang tidak hanya menunjukkan metode statis dengan implementasi, tetapi karena metode tersebut dipanggil Main dan memiliki jenis pengembalian dan tanda tangan yang tepat, metode ini juga merupakan titik masuk. catatan akhir

Metode virtual dengan implementasi yang dideklarasikan dalam antarmuka dapat ditimpa untuk menjadi abstrak dalam antarmuka turunan. Ini dikenal sebagai reabstraksi.

Contoh:

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

interface IB: IA
{
    abstract void IA.M();    // reabstraction of M
}

Ini berguna dalam antarmuka turunan di mana implementasi metode tidak tepat dan implementasi yang lebih tepat harus disediakan dengan menerapkan kelas. contoh akhir

19.4.4 Properti antarmuka

Klausa ini menambah deskripsi properti di kelas §15.7 untuk properti yang dideklarasikan dalam antarmuka.

Properti antarmuka dinyatakan menggunakan property_declaration(§15.7.1) dengan aturan tambahan berikut:

  • property_modifier tidak boleh menyertakan override.

  • Implementasi anggota antarmuka eksplisit tidak boleh berisi accessor_modifier (§15.7.3).

  • Antarmuka turunan dapat secara eksplisit mengimplementasikan properti antarmuka abstrak yang dideklarasikan dalam antarmuka dasar.

    Catatan: Karena antarmuka tidak dapat berisi bidang instans, properti antarmuka tidak dapat menjadi properti otomatis instans, karena akan memerlukan deklarasi bidang instans tersembunyi implisit. catatan akhir

  • Jenis properti antarmuka harus aman keluaran jika ada aksesor get, dan harus aman masukan jika ada aksesor set.

  • Deklarasi metode antarmuka yang memiliki badan blok atau badan ekspresi sebagai method_body adalah virtual; virtual pengubah tidak diperlukan, tetapi diizinkan.

  • Instans property_declaration yang tidak memiliki implementasi adalah abstract; pengubah abstract tidak diperlukan, tetapi diizinkan. Ini tidak pernah dianggap sebagai properti yang diimplementasikan secara otomatis (§15.7.4).

19.4.5 Peristiwa antarmuka

Klausa ini menambah deskripsi peristiwa di kelas §15.8 untuk peristiwa yang dideklarasikan dalam antarmuka.

Peristiwa antarmuka dinyatakan menggunakan event_declaration(§15.8.1), dengan aturan tambahan berikut:

  • event_modifier tidak boleh menyertakan override.
  • Antarmuka turunan dapat mengimplementasikan peristiwa antarmuka abstrak yang dideklarasikan dalam antarmuka dasar (§15.8.5).
  • Ini adalah kesalahan waktu kompilasi untuk variable_declarators dalam instans event_declaration untuk berisi variable_initializer.
  • Peristiwa instans dengan pengubah virtual atau sealed harus mendeklarasikan aksesor. Ini tidak pernah dianggap sebagai peristiwa seperti bidang yang diimplementasikan secara otomatis (§15.8.2).
  • Peristiwa instans dengan pengubah abstract tidak boleh mendeklarasikan aksesor.
  • Jenis peristiwa antarmuka harus aman untuk input.

19.4.6 Pengindeks antarmuka

Klausa ini menambah deskripsi pengindeks di kelas §15.9 untuk pengindeks yang dideklarasikan dalam antarmuka.

Pengindeks antarmuka dinyatakan menggunakan indexer_declaration(§15,9), dengan aturan tambahan berikut:

  • indexer_modifier tidak boleh menyertakan override.

  • indexer_declaration yang memiliki isi ekspresi atau berisi aksesor dengan isi blok atau isi ekspresi adalah ; virtual pengubah tidak diperlukan, tetapi diizinkan.virtual

  • Indexer_declaration yang badan aksesornya adalah titik koma (;) ; abstractabstract pengubah tidak diperlukan, tetapi diizinkan.

  • Semua jenis parameter pengindeks antarmuka harus aman input (§19.2.3.2).

  • Setiap jenis parameter output atau referensi juga harus aman output.

    Catatan: Parameter output diperlukan agar aman input karena pembatasan implementasi umum. catatan akhir

  • Jenis pengindeks antarmuka harus aman output jika ada aksesor get, dan harus aman input jika ada aksesor yang ditetapkan.

19.4.7 Operator antarmuka

Klausa ini menambah deskripsi anggota operator_declaration di kelas §15.10 untuk operator yang dideklarasikan dalam antarmuka.

Operator_declaration dalam antarmuka adalah implementasinya (§19.1).

Ini adalah kesalahan waktu kompilasi bagi antarmuka untuk mendeklarasikan operator konversi, kesetaraan, atau ketidaksetaraan.

19.4.8 Konstruktor statis antarmuka

Klausa ini menambah deskripsi konstruktor statis di kelas §15.12 untuk konstruktor statis yang dideklarasikan dalam antarmuka.

Konstruktor statis untuk antarmuka tertutup (§8.4.3) dijalankan paling banyak sekali di domain aplikasi tertentu. Eksekusi konstruktor statis dipicu oleh tindakan pertama berikut yang terjadi dalam domain aplikasi:

  • Salah satu anggota statis antarmuka dirujuk.
  • Sebelum metode dipanggil Main untuk antarmuka yang berisi Main metode (§7.1) di mana eksekusi dimulai.
  • Antarmuka tersebut menyediakan implementasi untuk anggota, dan implementasi tersebut diakses sebagai implementasi yang paling spesifik (§19.4.10) untuk anggota tersebut.

Catatan: Dalam kasus di mana tidak ada tindakan sebelumnya yang terjadi, konstruktor statis untuk antarmuka mungkin tidak dijalankan untuk program tempat instans jenis yang mengimplementasikan antarmuka dibuat dan digunakan. catatan akhir

Untuk menginisialisasi jenis antarmuka tertutup baru, pertama-tama sekumpulan bidang statis baru untuk jenis tertutup tertentu dibuat. Masing-masing bidang statis diinisialisasi ke nilai defaultnya. Selanjutnya, penginisialisasi bidang statis dijalankan untuk bidang statis tersebut. Akhirnya, konstruktor statis dijalankan.

Catatan: Lihat §19.4.2 untuk contoh penggunaan berbagai jenis anggota statis (termasuk metode Utama) yang dideklarasikan dalam antarmuka. catatan akhir

19.4.9 Jenis berlapis Antarmuka

Klausa ini menambah deskripsi jenis berlapis di kelas §15.3.9 untuk jenis berlapis yang dideklarasikan dalam antarmuka.

Ini adalah kesalahan untuk mendeklarasikan jenis kelas, jenis struct, atau jenis enum dalam cakupan parameter jenis yang dideklarasikan dengan variance_annotation (§19.2.3.1).

Contoh: Deklarasi C di bawah ini adalah kesalahan.

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

contoh akhir

19.4.10 implementasi yang paling spesifik

Setiap kelas dan struktur harus memiliki implementasi yang paling spesifik untuk setiap anggota virtual yang dideklarasikan di semua antarmuka yang diimplementasikan oleh jenis tersebut di antara implementasi yang muncul dalam jenis atau antarmuka langsung dan tidak langsungnya. Implementasi yang paling spesifik adalah implementasi unik yang lebih spesifik daripada setiap implementasi lainnya.

Catatan: Aturan implementasi yang paling spesifik memastikan bahwa ambiguitas yang timbul dari warisan antarmuka berlian diselesaikan secara eksplisit oleh programmer pada titik di mana konflik terjadi. catatan akhir

Untuk jenis T yang merupakan struct atau kelas yang mengimplementasikan antarmuka I2 dan I3, di mana I2 dan I3 keduanya berasal secara langsung atau tidak langsung dari antarmuka I yang mendeklarasikan anggota M, implementasi M yang paling spesifik adalah:

  • Jika T menyatakan implementasi I.M, implementasi tersebut adalah implementasi yang paling spesifik.
  • Jika tidak, jika T adalah kelas dan kelas dasar langsung atau tidak langsung menyatakan implementasi I.M, kelas dasar yang paling turunan adalah T implementasi yang paling spesifik.
  • Jika tidak, jika I2 dan I3 merupakan antarmuka yang diimplementasikan oleh T dan I3 berasal dari I2 baik secara langsung atau tidak langsung, I3.M adalah implementasi yang lebih spesifik daripada I2.M.
  • Jika tidak, tidak I2.M juga I3.M tidak lebih spesifik dan terjadi kesalahan.

Contoh:

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

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

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

abstract class C: IB, IC { } // error: no most specific implementation for 'IA.M'

abstract class D: IA, IB, IC // OK
{
    public abstract void M();
}

Aturan implementasi yang paling spesifik memastikan bahwa konflik (yaitu, ambiguitas yang timbul dari warisan berlian) diselesaikan secara eksplisit oleh programmer pada titik di mana konflik muncul. contoh akhir

19.4.11 Akses anggota antarmuka

Anggota antarmuka diakses melalui akses anggota (§12.8.7) dan akses pengindeks (§12.8.12.4) ekspresi formulir I.M dan I[A], di mana I merupakan jenis antarmuka, adalah konstanta, M bidang, metode, properti, atau peristiwa jenis antarmuka tersebut, dan A merupakan daftar argumen pengindeks.

Di kelas D, dengan kelas Bdasar langsung atau tidak langsung , di mana B secara langsung atau tidak langsung mengimplementasikan antarmuka I dan I menentukan metode M(), ekspresi base.M() hanya valid jika base.M() secara statis (§12,3) mengikat implementasi M() dalam jenis kelas.

Untuk antarmuka yang benar-benar warisan tunggal (setiap antarmuka dalam rantai warisan memiliki tepat nol atau satu antarmuka dasar langsung), efek pencarian anggota (§12,5), pemanggilan metode (§12..) 8.10.2), dan aturan akses pengindeks (§12.8.12.4) sama persis dengan untuk kelas dan struktur: Lebih banyak anggota turunan menyembunyikan anggota yang kurang diturunkan dengan nama atau tanda tangan yang sama. Namun, untuk antarmuka beberapa warisan, ambiguitas dapat terjadi ketika dua atau beberapa antarmuka dasar yang tidak terkait mendeklarasikan anggota dengan nama atau tanda tangan yang sama. Subklasul ini menunjukkan beberapa contoh, beberapa di antaranya menyebabkan ambiguitas dan lainnya yang tidak. Dalam semua kasus, pencastingan eksplisit dapat digunakan untuk menyelesaikan ambiguitas.

Contoh: Dalam kode berikut

interface IList
{
    int Count { get; set; }
}

interface ICounter
{
    int Count { get; set; }
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
        x.Count = 1;             // Error
        ((IList)x).Count = 1;    // Ok, invokes IList.Count.set
        ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count
    }
}

Pernyataan pertama menyebabkan kesalahan kompilasi waktu karena pencarian anggota (§12,5) untuk dalam Count yang ambigu. Seperti yang diilustrasikan oleh contoh, ambiguitas diselesaikan dengan mentransmisikan x ke jenis antarmuka dasar yang sesuai. Cast seperti itu tidak memiliki biaya run-time—hanya berarti melihat instans sebagai jenis yang lebih umum pada waktu kompilasi.

contoh akhir

Contoh: Dalam kode berikut

interface IInteger
{
    void Add(int i);
}

interface IDouble
{
    void Add(double d);
}

interface INumber : IInteger, IDouble {}

class C
{
    void Test(INumber n)
    {
        n.Add(1);             // Invokes IInteger.Add
        n.Add(1.0);           // Only IDouble.Add is applicable
        ((IInteger)n).Add(1); // Only IInteger.Add is a candidate
        ((IDouble)n).Add(1);  // Only IDouble.Add is a candidate
    }
}

pemanggilan n.Add(1) memilih IInteger.Add dengan menerapkan aturan resolusi kelebihan beban §12.6.4. Demikian pula, pemanggilan n.Add(1.0) memilih IDouble.Add. Ketika cast eksplisit dimasukkan, hanya ada satu metode yang mungkin dipilih, dan dengan demikian tidak ada ambiguitas.

contoh akhir

Contoh: Dalam kode berikut

interface IBase
{
    void F(int i);
}

interface ILeft : IBase
{
    new void F(int i);
}

interface IRight : IBase
{
    void G();
}

interface IDerived : ILeft, IRight {}

class A
{
    void Test(IDerived d)
    {
        d.F(1);           // Invokes ILeft.F
        ((IBase)d).F(1);  // Invokes IBase.F
        ((ILeft)d).F(1);  // Invokes ILeft.F
        ((IRight)d).F(1); // Invokes IBase.F
    }
}

IBase.F anggota disembunyikan oleh anggota ILeft.F. Pemanggilan d.F(1) oleh karena itu menentukan ILeft.F, meskipun IBase.F tampaknya tidak disembunyikan di jalur akses yang mengarah melalui IRight.

Aturan intuitif untuk menyembunyikan dalam antarmuka pewarisan ganda adalah sederhana: Jika anggota disembunyikan di jalur akses mana pun, anggota tersebut disembunyikan di semua jalur akses. Karena jalur akses dari IDerived ke ILeft ke IBase menyembunyikan IBase.F, anggota juga tersembunyi di jalur akses dari IDerived ke IRight ke IBase.

contoh akhir

19.5 Nama anggota antarmuka yang memenuhi syarat

Anggota antarmuka terkadang disebut dengan nama anggota antarmuka yang memenuhi syarat. Nama lengkap dari anggota antarmuka terdiri dari nama antarmuka tempat anggota dideklarasikan, diikuti oleh titik, diikuti dengan nama anggota. Nama anggota yang memenuhi syarat mereferensikan antarmuka tempat anggota dideklarasikan.

Contoh: Berdasarkan deklarasi

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

nama lengkap dari Paint adalah IControl.Paint dan nama lengkap dari SetText adalah ITextBox.SetText. Dalam contoh di atas, tidak mungkin untuk menyebutnya Paint sebagai ITextBox.Paint.

contoh akhir

Saat antarmuka adalah bagian dari namespace layanan, nama anggota antarmuka yang memenuhi syarat dapat menyertakan nama namespace layanan.

Contoh:

namespace GraphicsLib
{
    interface IPolygon
    {
        void CalculateArea();
    }
}

Di dalam namespace GraphicsLib, IPolygon.CalculateArea dan GraphicsLib.IPolygon.CalculateArea keduanya merupakan nama anggota antarmuka yang memenuhi syarat untuk metode CalculateArea.

contoh akhir

19.6 Implementasi antarmuka

19.6.1 Umum

Antarmuka dapat diimplementasikan oleh kelas dan struktur. Untuk menunjukkan bahwa kelas atau struktur secara langsung mengimplementasikan antarmuka, antarmuka disertakan dalam daftar kelas dasar kelas atau struktur.

Kelas atau struktur C yang mengimplementasikan antarmuka I harus menyediakan atau mewarisi implementasi untuk setiap anggota yang dinyatakan dalam I yang C dapat mengakses. Anggota I publik dapat didefinisikan dalam anggota publik .C Anggota non-publik yang dinyatakan dalam I yang dapat diakses C dapat didefinisikan dalam C menggunakan implementasi antarmuka eksplisit (§19.6.2).

Anggota dalam jenis turunan yang memenuhi pemetaan antarmuka (§19.6.5) tetapi tidak menerapkan anggota antarmuka dasar yang cocok memperkenalkan anggota baru. Ini terjadi ketika implementasi antarmuka eksplisit diperlukan untuk menentukan anggota antarmuka.

Contoh:

interface ICloneable
{
    object Clone();
}

interface IComparable
{
    int CompareTo(object other);
}

class ListEntry : ICloneable, IComparable
{
    public object Clone() {...}    
    public int CompareTo(object other) {...}
}

contoh akhir

Kelas atau struktur yang secara langsung mengimplementasikan antarmuka juga secara implisit mengimplementasikan semua antarmuka dasar antarmuka. Ini berlaku bahkan jika kelas atau struktur tidak secara eksplisit mencantumkan semua antarmuka dasar dalam daftar kelas dasar.

Contoh:

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    public void Paint() {...}
    public void SetText(string text) {...}
}

Di sini, kelas TextBox mengimplementasikan IControl dan ITextBox.

contoh akhir

Ketika kelas C secara langsung mengimplementasikan antarmuka, semua kelas yang berasal dari C juga mengimplementasikan antarmuka secara implisit.

Antarmuka dasar yang ditentukan dalam deklarasi kelas dapat dibangun jenis antarmuka (§8.4, §19.2).

Contoh: Kode berikut menggambarkan bagaimana kelas dapat mengimplementasikan jenis antarmuka yang dibangun:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

contoh akhir

Antarmuka dasar deklarasi kelas generik harus memenuhi aturan keunikan yang dijelaskan dalam §19.6.3.

19.6.2 Implementasi anggota antarmuka eksplisit

Untuk tujuan menerapkan antarmuka, kelas, struct, atau antarmuka dapat mendeklarasikan implementasi anggota antarmuka eksplisit. Implementasi anggota antarmuka eksplisit adalah metode, properti, peristiwa, atau deklarasi pengindeks yang mereferensikan nama anggota antarmuka yang memenuhi syarat. Kelas atau struktur yang mengimplementasikan anggota non-publik dalam antarmuka dasar harus mendeklarasikan implementasi anggota antarmuka eksplisit. Antarmuka yang mengimplementasikan anggota dalam antarmuka dasar harus mendeklarasikan implementasi anggota antarmuka eksplisit.

Anggota antarmuka turunan yang memenuhi pemetaan antarmuka (§19.6.5) menyembunyikan anggota antarmuka dasar (§7.7.2). Pengkompilasi akan mengeluarkan peringatan kecuali pengubah new ada.

Contoh:

interface IList<T>
{
    T[] GetElements();
}

interface IDictionary<K, V>
{
    V this[K key] { get; }
    void Add(K key, V value);
}

class List<T> : IList<T>, IDictionary<int, T>
{
    public T[] GetElements() {...}
    T IDictionary<int, T>.this[int index] {...}
    void IDictionary<int, T>.Add(int index, T value) {...}
}

Berikut IDictionary<int,T>.this dan IDictionary<int,T>.Add adalah implementasi anggota antarmuka eksplisit.

contoh akhir

Contoh: Dalam beberapa kasus, nama anggota antarmuka mungkin tidak sesuai untuk kelas penerapan, dalam hal ini, anggota antarmuka dapat diimplementasikan menggunakan implementasi anggota antarmuka eksplisit. Kelas yang menerapkan abstraksi file, misalnya, kemungkinan akan mengimplementasikan fungsi anggota Close yang memiliki efek melepaskan sumber daya file, dan menerapkan metode antarmuka Dispose dengan menggunakan implementasi anggota antarmuka eksplisit:

interface IDisposable
{
    void Dispose();
}

class MyFile : IDisposable
{
    void IDisposable.Dispose() => Close();

    public void Close()
    {
        // Do what's necessary to close the file
        System.GC.SuppressFinalize(this);
    }
}

contoh akhir

Tidak memungkinkan untuk mengakses implementasi anggota antarmuka eksplisit melalui nama anggota antarmuka yang telah ditentukan dalam pemanggilan metode, akses properti, akses peristiwa, atau akses pengindeks. Implementasi anggota instans antarmuka eksplisit hanya dapat diakses melalui instans antarmuka, dan dalam hal ini dirujuk hanya dengan nama anggotanya. Implementasi anggota statis antarmuka eksplisit hanya dapat diakses melalui nama antarmuka.

Kesalahan terjadi pada waktu kompilasi jika implementasi anggota antarmuka eksplisit menyertakan pengubah apa pun (§15.6) selain extern atau async.

Implementasi metode antarmuka eksplisit mewarisi batasan parameter jenis apa pun dari antarmuka.

Type_parameter_constraints_clause pada implementasi metode antarmuka eksplisit hanya dapat terdiri dari class atau structprimary_constraintyang diterapkan pada type_parameteryang diketahui sesuai dengan batasan yang diwariskan untuk menjadi jenis referensi atau nilai masing-masing. Semua jenis formulir T? dalam tanda tangan implementasi metode antarmuka eksplisit, di mana T adalah parameter jenis, ditafsirkan sebagai berikut:

  • class Jika batasan ditambahkan untuk parameter jenis T, maka T? adalah tipe referensi nullable; atau sebaliknya,
  • Jika tidak ada batasan tambahan, atau struct batasan ditambahkan, untuk parameter jenis T, maka T? adalah tipe nilai nullable.

Contoh: Berikut ini menunjukkan cara kerja aturan saat parameter jenis terlibat:

#nullable enable
interface I
{
    void Foo<T>(T? value) where T : class;
    void Foo<T>(T? value) where T : struct;
}

class C : I
{
    void I.Foo<T>(T? value) where T : class { }
    void I.Foo<T>(T? value) where T : struct { }
}

Tanpa batasan parameter jenis where T : class, metode dasar dengan parameter jenis tipe referensi tidak dapat di-override. contoh akhir

Catatan: Implementasi anggota antarmuka eksplisit memiliki karakteristik aksesibilitas yang berbeda dari anggota lain. Karena implementasi anggota antarmuka eksplisit tidak pernah dapat diakses melalui nama anggota antarmuka yang memenuhi syarat saat pemanggilan metode atau saat mengakses properti, mereka bersifat privat dalam pengertian tertentu. Namun, karena mereka dapat diakses melalui antarmuka, mereka, dalam hal ini, sama terbukanya dengan antarmuka tempat mereka dideklarasikan. Implementasi anggota antarmuka eksplisit melayani dua tujuan utama:

  • Karena implementasi anggota antarmuka yang eksplisit tidak dapat diakses melalui instans kelas atau struct, mereka memungkinkan agar implementasi antarmuka ini dikecualikan dari antarmuka publik kelas atau struct. Ini sangat berguna ketika kelas atau struktur mengimplementasikan antarmuka internal yang tidak menarik bagi konsumen kelas atau struktur tersebut.
  • Implementasi anggota antarmuka eksplisit memungkinkan disambiguasi anggota antarmuka dengan tanda tangan yang sama. Tanpa implementasi anggota antarmuka eksplisit, tidak mungkin bagi kelas, struktur, atau antarmuka untuk memiliki implementasi anggota antarmuka yang berbeda dengan tanda tangan dan jenis pengembalian yang sama, karena tidak mungkin bagi kelas, struktur, atau antarmuka untuk memiliki implementasi apa pun di semua anggota antarmuka dengan tanda tangan yang sama tetapi dengan jenis pengembalian yang berbeda.

catatan akhir

Agar implementasi anggota antarmuka eksplisit valid, kelas, struktur, atau antarmuka harus memberi nama antarmuka di kelas dasar atau daftar antarmuka dasar yang berisi anggota yang nama anggota antarmuka yang memenuhi syarat, jenis, jumlah parameter jenis, dan jenis parameter yang sama persis dengan implementasi anggota antarmuka eksplisit. Jika anggota fungsi antarmuka memiliki array parameter, parameter yang sesuai dari implementasi anggota antarmuka eksplisit terkait diizinkan, tetapi tidak diperlukan, untuk memiliki params pengubah. Jika anggota fungsi antarmuka tidak memiliki array parameter, implementasi anggota antarmuka eksplisit terkait tidak akan memiliki array parameter.

Contoh: Dengan demikian, di kelas berikut

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
    int IComparable.CompareTo(object other) {...} // invalid
}

deklarasi hasil dalam kesalahan waktu kompilasi IComparable.CompareTo karena IComparable tidak tercantum dalam daftar Shape kelas dasar dan bukan antarmuka ICloneabledasar . Demikian juga, dalam deklarasi

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
}

class Ellipse : Shape
{
    object ICloneable.Clone() {...} // invalid
}

deklarasi ICloneable.Clone dalam Ellipse menghasilkan kesalahan waktu kompilasi karena ICloneable tidak secara eksplisit tercantum dalam daftar kelas dasar Ellipse.

contoh akhir

Nama anggota antarmuka yang memenuhi syarat dari implementasi anggota antarmuka eksplisit harus mereferensikan antarmuka tempat anggota dideklarasikan.

Contoh: Dengan demikian, dalam deklarasi

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
}

implementasi anggota antarmuka eksplisit dari Paint harus ditulis sebagai IControl.Paint, bukan ITextBox.Paint.

contoh akhir

19.6.3 Keunikan antarmuka yang diimplementasikan

Antarmuka yang diimplementasikan oleh deklarasi jenis generik harus tetap unik untuk semua kemungkinan jenis yang dibangun. Tanpa aturan ini, tidak mungkin untuk menentukan metode yang benar untuk memanggil jenis tertentu yang dibangun.

Contoh: Misalkan deklarasi kelas generik diizinkan untuk ditulis sebagai berikut:

interface I<T>
{
    void F();
}

class X<U ,V> : I<U>, I<V> // Error: I<U> and I<V> conflict
{
    void I<U>.F() {...}
    void I<V>.F() {...}
}

Jika ini diizinkan, tidak mungkin untuk menentukan kode mana yang akan dijalankan dalam kasus berikut:

I<int> x = new X<int, int>();
x.F();

contoh akhir

Untuk menentukan apakah daftar antarmuka deklarasi jenis generik valid, langkah-langkah berikut dilakukan:

  • Biarkan L menjadi daftar antarmuka yang secara langsung ditentukan di dalam kelas generik, struct, atau deklarasi C.
  • Tambahkan ke L antarmuka dasar antarmuka apa pun yang sudah ada di L.
  • Hapus duplikat apa pun dari L.
  • Jika ada kemungkinan jenis yang mungkin dibuat dari C akan, setelah argumen jenis diganti menjadi L, menyebabkan dua antarmuka dalam L menjadi identik, maka deklarasi C tidak valid. Deklarasi batasan tidak dipertimbangkan saat menentukan semua kemungkinan jenis yang dibangun.

Catatan: Dalam deklarasi X kelas di atas, daftar L antarmuka terdiri dari l<U> dan I<V>. Deklarasi tidak valid karena jenis yang dibuat dengan U dan V menjadi jenis yang sama akan menyebabkan kedua antarmuka ini menjadi jenis yang identik. catatan akhir

Dimungkinkan untuk antarmuka yang ditentukan pada tingkat pewarisan yang berbeda untuk menyatukan:

interface I<T>
{
    void F();
}

class Base<U> : I<U>
{
    void I<U>.F() {...}
}

class Derived<U, V> : Base<U>, I<V> // Ok
{
    void I<V>.F() {...}
}

Kode ini valid meskipun Derived<U,V> mengimplementasikan baik I<U> maupun I<V>. Kode

I<int> x = new Derived<int, int>();
x.F();

memanggil metode dalam Derived, karena Derived<int,int>' secara efektif mengimplementasikan I<int> ulang (§19.6.7).

19.6.4 Implementasi metode generik

Ketika metode generik secara implisit menerapkan metode antarmuka, batasan yang diberikan untuk setiap parameter jenis metode harus setara dalam kedua deklarasi (setelah parameter jenis antarmuka diganti dengan argumen jenis yang sesuai), di mana parameter jenis metode diidentifikasi oleh posisi ordinal, kiri ke kanan.

Contoh: Dalam kode berikut:

interface I<X, Y, Z>
{
    void F<T>(T t) where T : X;
    void G<T>(T t) where T : Y;
    void H<T>(T t) where T : Z;
}

class C : I<object, C, string>
{
    public void F<T>(T t) {...}                  // Ok
    public void G<T>(T t) where T : C {...}      // Ok
    public void H<T>(T t) where T : string {...} // Error
}

metode C.F<T> secara implisit mengimplementasikan I<object,C,string>.F<T>. Dalam hal ini, C.F<T> tidak diperlukan (atau diizinkan) untuk menentukan batasan T: object karena object merupakan batasan implisit pada semua parameter jenis. Metode C.G<T> secara implisit mengimplementasikan I<object,C,string>.G<T> karena batasannya sesuai dengan yang ada di antarmuka, setelah parameter tipe pada antarmuka diganti dengan argumen tipe yang sesuai. Batasan untuk metode C.H<T> adalah kesalahan karena jenis yang disegel (string dalam hal ini) tidak dapat digunakan sebagai batasan. Menghilangkan batasan juga akan menjadi kesalahan karena batasan implementasi metode antarmuka implisit diperlukan untuk dicocokkan. Dengan demikian, tidak mungkin untuk mengimplementasikan I<object,C,string>.H<T>secara implisit . Metode antarmuka ini hanya dapat diimplementasikan menggunakan implementasi anggota antarmuka eksplisit:

class C : I<object, C, string>
{
    ...
    public void H<U>(U u) where U : class {...}

    void I<object, C, string>.H<T>(T t)
    {
        string s = t; // Ok
        H<T>(t);
    }
}

Dalam hal ini, implementasi anggota antarmuka eksplisit memanggil metode publik yang memiliki batasan yang sangat lebih lemah. Penugasan dari t ke s valid karena T mewarisi batasan T: string, meskipun batasan ini tidak dapat diekspresikan dalam kode sumber. contoh akhir

Catatan: Ketika metode generik secara eksplisit menerapkan metode antarmuka, tidak ada batasan yang diizinkan pada metode penerapan (§15.7.1, §19.6.2). catatan akhir

Pemetaan antarmuka 19.6.5

Kelas atau struktur harus menyediakan implementasi semua anggota abstrak antarmuka yang tercantum dalam daftar kelas dasar kelas atau struktur. Proses menemukan implementasi anggota antarmuka di kelas atau struktur penerapan dikenal sebagai pemetaan antarmuka.

Pemetaan antarmuka untuk kelas atau struct C menemukan implementasi untuk setiap anggota dari setiap antarmuka yang ditentukan dalam daftar kelas dasar C. Implementasi anggota I.Mantarmuka tertentu , di mana I adalah antarmuka di mana anggota M dideklarasikan, ditentukan dengan memeriksa setiap kelas, antarmuka, atau struct S, dimulai dengan C dan mengulangi untuk setiap kelas dasar berturut-turut dan antarmuka yang diimplementasikan dari C, sampai kecocokan terletak:

  • Jika S berisi deklarasi implementasi anggota antarmuka eksplisit yang cocok I dan M, maka anggota ini adalah implementasi dari I.M.
  • Jika tidak, jika S berisi deklarasi anggota publik non-statis yang cocok M, maka anggota ini adalah implementasi dari I.M. Jika lebih dari satu anggota cocok, tidak ditentukan anggota mana yang merupakan implementasi dari I.M. Situasi ini hanya dapat terjadi jika S adalah jenis yang dibangun di mana kedua anggota seperti yang dinyatakan dalam jenis generik memiliki tanda tangan yang berbeda, tetapi argumen jenis membuat tanda tangan mereka identik.

Kesalahan waktu kompilasi terjadi jika implementasi tidak dapat ditemukan untuk semua anggota dari semua antarmuka yang ditentukan dalam daftar Ckelas dasar . Anggota antarmuka mencakup anggota yang diwarisi dari antarmuka dasar.

Anggota dari tipe antarmuka yang dibentuk dianggap memiliki parameter tipe apa pun yang diganti dengan argumen tipe yang sesuai seperti yang ditentukan dalam §15.3.3.

Contoh: Misalnya, mengingat deklarasi antarmuka generik:

interface I<T>
{
    T F(int x, T[,] y);
    T this[int y] { get; }
}

antarmuka I<string[]> yang dibangun memiliki anggota:

string[] F(int x, string[,][] y);
string[] this[int y] { get; }

contoh akhir

Untuk tujuan pemetaan antarmuka, kelas, antarmuka, atau anggota A struct cocok dengan anggota B antarmuka saat:

  • A dan B merupakan metode, serta nama, jenis, dan daftar parameter dan AB identik.
  • A dan B merupakan properti, nama dan jenis A dan B identik, dan A memiliki pengakses yang sama dengan B (A diizinkan untuk memiliki aksesor tambahan jika bukan implementasi anggota antarmuka eksplisit).
  • A dan B merupakan peristiwa, serta nama dan jenis A serta B identik.
  • A dan B adalah pengindeks, jenis dan daftar A parameter dan B identik, dan A memiliki pengakses yang sama dengan B (A diizinkan untuk memiliki aksesor tambahan jika bukan implementasi anggota antarmuka eksplisit).

Implikasi penting dari algoritma pemetaan antarmuka adalah:

  • Implementasi anggota antarmuka eksplisit lebih diutamakan daripada anggota lain di kelas atau struktur yang sama saat menentukan anggota kelas atau struktur yang mengimplementasikan anggota antarmuka.
  • Baik non-publik maupun anggota statis tidak berpartisipasi dalam pemetaan antarmuka.

Contoh: Dalam kode berikut

interface ICloneable
{
    object Clone();
}

class C : ICloneable
{
    object ICloneable.Clone() {...}
    public object Clone() {...}
}

anggota ICloneable.Clone dari C mengimplementasikan Clone dalam ICloneable karena implementasi anggota antarmuka yang eksplisit lebih diutamakan daripada anggota lain.

contoh akhir

Jika kelas atau struct mengimplementasikan dua antarmuka atau lebih yang berisi anggota dengan nama, jenis, dan jenis parameter yang sama, dimungkinkan untuk memetakan masing-masing anggota antarmuka tersebut ke satu kelas atau anggota struct.

Contoh:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

class Page : IControl, IForm
{
    public void Paint() {...}
}

Pada konteks ini, metode Paint dari IControl dan IForm dipetakan ke metode Paint di Page. Tentu saja juga dimungkinkan untuk memiliki implementasi anggota antarmuka eksplisit terpisah untuk dua metode tersebut.

contoh akhir

Jika kelas atau struct mengimplementasikan antarmuka yang berisi anggota tersembunyi, beberapa anggota mungkin perlu diimplementasikan melalui implementasi anggota antarmuka eksplisit.

Contoh:

interface IBase
{
    int P { get; }
}

interface IDerived : IBase
{
    new int P();
}

Implementasi antarmuka ini akan memerlukan setidaknya satu implementasi anggota antarmuka eksplisit, dan akan mengambil salah satu bentuk berikut

class C1 : IDerived
{
    int IBase.P { get; }
    int IDerived.P() {...}
}
class C2 : IDerived
{
    public int P { get; }
    int IDerived.P() {...}
}
class C3 : IDerived
{
    int IBase.P { get; }
    public int P() {...}
}

contoh akhir

Ketika kelas mengimplementasikan beberapa antarmuka yang memiliki antarmuka dasar yang sama, hanya ada satu implementasi antarmuka dasar.

Contoh: Dalam kode berikut

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

class ComboBox : IControl, ITextBox, IListBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
    void IListBox.SetItems(string[] items) {...}
}

tidak dimungkinkan untuk memiliki implementasi terpisah untuk yang dinamai IControl dalam daftar kelas dasar, yang IControl diwariskan oleh ITextBox, dan yang IControl diwariskan oleh IListBox. Memang, tidak ada gagasan tentang identitas terpisah untuk antarmuka ini. Sebaliknya, implementasi ITextBoxdan IListBox berbagi implementasi yang sama dari IControl, dan ComboBox hanya dianggap untuk mengimplementasikan tiga antarmuka, IControl, , ITextBoxdan IListBox.

contoh akhir

Anggota kelas dasar berpartisipasi dalam pemetaan antarmuka.

Contoh: Dalam kode berikut

interface Interface1
{
    void F();
}

class Class1
{
    public void F() {}
    public void G() {}
}

class Class2 : Class1, Interface1
{
    public new void G() {}
}

metode F yang digunakan dalam implementasi Class1 dari Class2'sInterface1.

contoh akhir

19.6.6 Pewarisan implementasi antarmuka

Kelas mewarisi semua implementasi antarmuka yang disediakan oleh kelas dasarnya.

Tanpa menerapkan kembali antarmuka secara eksplisit, kelas turunan tidak dapat dengan cara apa pun mengubah pemetaan antarmuka yang diwarisinya dari kelas dasarnya.

Contoh: Dalam deklarasi

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public void Paint() {...}
}

class TextBox : Control
{
    public new void Paint() {...}
}

Paint metode dalam TextBox menyembunyikan Paint metode di Control, tetapi tidak mengubah pemetaan Control.Paint ke IControl.Paint, dan panggilan ke Paint melalui instans kelas dan instans antarmuka akan memiliki efek berikut

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();

contoh akhir

Namun, ketika metode antarmuka dipetakan ke metode virtual di kelas, dimungkinkan bagi kelas turunan untuk mengambil alih metode virtual dan mengubah implementasi antarmuka.

Contoh: Menulis ulang deklarasi di atas ke

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public virtual void Paint() {...}
}

class TextBox : Control
{
    public override void Paint() {...}
}

efek berikut sekarang akan diamati

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes TextBox.Paint();

contoh akhir

Karena implementasi anggota antarmuka eksplisit tidak dapat dinyatakan virtual, tidak mungkin untuk mengambil alih implementasi anggota antarmuka eksplisit. Namun, sangat valid untuk implementasi anggota antarmuka eksplisit untuk memanggil metode lain, dan bahwa metode lain dapat dinyatakan virtual untuk memungkinkan kelas turunan mengambil alihnya.

Contoh:

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() { PaintControl(); }
    protected virtual void PaintControl() {...}
}

class TextBox : Control
{
    protected override void PaintControl() {...}
}

Di sini, kelas yang berasal dari Control dapat mengkhasanalisasi implementasi IControl.Paint dengan mengesampingkan PaintControl metode .

contoh akhir

19.6.7 Implementasi ulang antarmuka

Kelas yang mewarisi implementasi antarmuka diizinkan untuk mengimplementasikan ulang antarmuka dengan menyertakannya dalam daftar kelas dasar.

Implementasi ulang antarmuka mengikuti aturan pemetaan antarmuka yang sama persis dengan implementasi awal antarmuka. Dengan demikian, pemetaan antarmuka yang diwariskan tidak berpengaruh apa pun pada pemetaan antarmuka yang ditetapkan untuk implementasi ulang antarmuka.

Contoh: Dalam deklarasi

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() {...}
}

class MyControl : Control, IControl
{
    public void Paint() {}
}

fakta bahwa Control memetakan IControl.Paint ke Control.IControl.Paint tidak memengaruhi implementasi ulang di MyControl, yang memetakan IControl.Paint ke MyControl.Paint.

contoh akhir

Deklarasi anggota publik yang diwarisi dan deklarasi anggota antarmuka eksplisit yang diwarisi ikut serta dalam proses pemetaan antarmuka untuk antarmuka yang diterapkan kembali.

Contoh:

interface IMethods
{
    void F();
    void G();
    void H();
    void I();
}

class Base : IMethods
{
    void IMethods.F() {}
    void IMethods.G() {}
    public void H() {}
    public void I() {}
}

class Derived : Base, IMethods
{
    public void F() {}
    void IMethods.H() {}
}

Di sini, implementasi IMethods dalam Derived memetakan metode antarmuka ke Derived.F, , Base.IMethods.G, Derived.IMethods.Hdan Base.I.

contoh akhir

Ketika kelas mengimplementasikan antarmuka, secara implisit juga mengimplementasikan semua antarmuka dasar antarmuka tersebut. Demikian juga, implementasi ulang antarmuka juga secara implisit merupakan implementasi ulang semua antarmuka dasar antarmuka.

Contoh:

interface IBase
{
    void F();
}

interface IDerived : IBase
{
    void G();
}

class C : IDerived
{
    void IBase.F() {...}
    void IDerived.G() {...}
}

class D : C, IDerived
{
    public void F() {...}
    public void G() {...}
}

Di sini, implementasi IDerived ulang juga mengimplementasikan IBaseulang , pemetaan IBase.F ke D.F.

contoh akhir

19.6.8 Kelas dan antarmuka abstrak

Seperti kelas non-abstrak, kelas abstrak harus menyediakan implementasi semua anggota abstrak antarmuka yang tercantum dalam daftar kelas dasar kelas. Namun, kelas abstrak diizinkan untuk memetakan metode antarmuka ke metode abstrak.

Contoh:

interface IMethods
{
    void F();
    void G();
}

abstract class C : IMethods
{
    public abstract void F();
    public abstract void G();
    }

Di sini, implementasi IMethods memetakan F dan G ke dalam metode-metode abstrak, yang harus ditimpa dalam kelas-kelas non-abstrak yang diturunkan dari C.

contoh akhir

Implementasi anggota antarmuka eksplisit tidak dapat abstrak, tetapi implementasi anggota antarmuka eksplisit tentu saja diizinkan untuk memanggil metode abstrak.

Contoh:

interface IMethods
{
    void F();
    void G();
}

abstract class C: IMethods
{
    void IMethods.F() { FF(); }
    void IMethods.G() { GG(); }
    protected abstract void FF();
    protected abstract void GG();
}

Di sini, kelas non-abstrak yang berasal C akan diperlukan untuk mengambil alih FF dan GG, sehingga memberikan implementasi aktual dari IMethods.

contoh akhir