Bagikan melalui


Pengembalian kovarian

Nota

Artikel ini adalah spesifikasi fitur. Spesifikasi berfungsi sebagai dokumen desain untuk fitur tersebut. Ini termasuk perubahan spesifikasi yang diusulkan, bersama dengan informasi yang diperlukan selama desain dan pengembangan fitur. Artikel ini diterbitkan sampai perubahan spesifikasi yang diusulkan diselesaikan dan dimasukkan dalam spesifikasi ECMA saat ini.

Mungkin ada beberapa perbedaan antara spesifikasi fitur dan implementasi yang selesai. Perbedaan tersebut diambil dalam catatan rapat desain bahasa (LDM) terkait.

Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .

Isu Juara: https://github.com/dotnet/csharplang/issues/49

Ringkasan

Mendukung jenis pengembalian kovarian. Secara khusus, izinkan method override untuk mendeklarasikan tipe pengembalian yang lebih turunan dibandingkan dengan metode yang di-over-ride, dan juga mengizinkan override properti hanya baca untuk mendeklarasikan tipe yang lebih turunan. Penimpaan deklarasi yang muncul dalam jenis turunan yang lebih akan diperlukan untuk memberikan jenis pengembalian paling tidak sejelas yang muncul dalam penimpaan pada jenis dasarnya. Pemanggil metode atau properti akan secara statis menerima jenis pengembalian yang lebih spesifik dari sebuah pemanggilan.

Motivasi

Ini adalah pola umum dalam pemrograman di mana nama metode yang berbeda harus diciptakan untuk mengatasi batasan bahasa bahwa override harus mengembalikan jenis yang sama dengan metode yang ditimpa.

Ini akan berguna dalam pola desain pabrik. Misalnya, dalam basis kode Roslyn kita akan memiliki

class Compilation ...
{
    public virtual Compilation WithOptions(Options options)...
}
class CSharpCompilation : Compilation
{
    public override CSharpCompilation WithOptions(Options options)...
}

Desain terperinci

Ini adalah spesifikasi untuk jenis pengembalian kovarian di C#. Tujuan kami adalah untuk mengizinkan penimpaan metode agar dapat mengembalikan jenis pengembalian yang lebih turunan daripada metode yang ditimpanya, serta mengizinkan penimpaan properti baca-saja untuk mengembalikan jenis pengembalian yang lebih turunan. Pemanggil metode atau properti akan menerima tipe pengembalian yang lebih terdefinisi dengan statis dari sebuah pemanggilan, dan penimpaan yang muncul dalam tipe yang lebih turunan akan diperlukan untuk memberikan tipe pengembalian yang setidaknya sama spesifiknya dengan yang muncul di tipe dasarnya.


Penimpaan Metode Kelas

Batasan yang ada pada metode penggantian kelas (§15.6.5)

  • Metode override dan metode dasar yang dioverride memiliki tipe pengembalian yang sama.

dimodifikasi menjadi

  • Metode penimpaan harus memiliki jenis pengembalian yang dapat dikonversi oleh konversi identitas atau (jika metode memiliki pengembalian nilai - bukan pengembalian ref melihat §13.1.0,5 konversi referensi implisit ke jenis pengembalian metode dasar yang ditimpa.

Dan persyaratan tambahan berikut ditambahkan ke daftar tersebut:

  • Metode override harus memiliki tipe pengembalian yang dapat dikonversi oleh konversi identitas atau (jika metode memiliki pengembalian nilai - bukan pengembalian ref , §13.1.0.5) secara implisit dikonversi referensi ke tipe pengembalian setiap override dari metode dasar yang ditimpa yang dideklarasikan dalam jenis dasar (langsung atau tidak langsung) dari metode override tersebut.
  • Jenis pengembalian metode override harus setidaknya sama dapat diaksesnya seperti metode override itu sendiri (Domain aksesibilitas - §7.5.3).

Batasan ini memungkinkan metode penimpaan di kelas private dapat memiliki jenis pengembalian private. Namun, metode penimpaan public dalam tipe public harus memiliki tipe pengembalian public.

Properti Kelas dan Penimpaan Pengindeks

Batasan yang ada pada properti dalam penimpaan kelas (§15.7.6)

Pernyataan properti penggantian harus menentukan pengubah aksesibilitas dan nama yang sama persis dengan properti yang diwariskan, dan akan ada konversi identitas antara jenis penggantian dan properti yang diwariskan. Jika properti yang diwariskan hanya memiliki aksesor tunggal (yaitu, jika properti yang diwariskan bersifat baca-saja atau tulis-saja), properti penimpaan hanya boleh menyertakan aksesor tersebut. Jika properti yang diwariskan mencakup kedua aksesor (yaitu, jika properti yang diwariskan adalah baca-tulis), properti yang menimpa dapat mencakup satu aksesor atau kedua aksesor.

dimodifikasi menjadi

Pernyataan deklarasi properti penggantian harus menentukan pengubah aksesibilitas dan nama yang sama persis dengan properti yang diwariskan, serta harus ada konversi identitas atau (jika properti yang diwariskan bersifat baca-saja dan memiliki pengembalian nilai - bukan pengembalian referensi §13.1.0.5) konversi referensi implisit dari tipe properti penggantian ke tipe properti yang diwariskan. Jika properti yang diwariskan hanya memiliki aksesor tunggal (yaitu, jika properti yang diwariskan bersifat baca-saja atau tulis-saja), properti penimpaan hanya boleh menyertakan aksesor tersebut. Jika properti yang diwariskan mencakup kedua aksesor (yaitu, jika properti yang diwariskan adalah baca-tulis), properti yang menimpa dapat mencakup satu aksesor atau kedua aksesor. Jenis properti pada properti penggantian harus setidaknya sama dapat diaksesnya dengan properti yang digantikan (Domain aksesibilitas - §7.5.3).


Sisa spesifikasi draf di bawah ini mengusulkan ekstensi lebih lanjut untuk pengembalian metode antarmuka yang kovarian untuk dipertimbangkan nanti.

Metode Antarmuka, Properti, dan Penimpaan Pengindeks

Menambah jenis anggota yang diperbolehkan dalam antarmuka dengan penambahan fitur DIM di C# 8.0, kami juga menambahkan dukungan untuk anggota override serta pengembalian kovarian. Ini mengikuti aturan anggota override seperti yang ditentukan untuk kelas, dengan perbedaan berikut:

Teks berikut di kelas:

Metode dasar yang ditimpa oleh deklarasi penimpaan dikenal sebagai metode dasar . Untuk metode override M yang dinyatakan dalam kelas C, metode dasar yang ditimpanya ditentukan dengan memeriksa setiap kelas dasar dari C, dimulai dengan kelas dasar langsung C dan melanjutkan dengan setiap kelas dasar langsung berikutnya, sampai dalam tipe kelas dasar yang diberikan setidaknya ditemukan satu metode yang dapat diakses yang memiliki tanda tangan yang sama dengan M setelah substitusi argumen jenis.

diberikan spesifikasi yang sesuai untuk antarmuka:

Metode dasar yang ditimpa oleh deklarasi penimpaan dikenal sebagai metode dasar . Untuk metode override M yang dinyatakan dalam antarmuka I, metode dasar yang dioverride ditentukan dengan memeriksa setiap antarmuka dasar langsung atau tidak langsung dari I, mengumpulkan antarmuka yang mendeklarasikan metode dengan tanda tangan yang sama dengan M setelah penyesuaian substitusi argumen jenis. Jika sekumpulan antarmuka ini memiliki jenis paling turunan , terdapat konversi referensi atau identitas secara implisit dari setiap jenis dalam set ini, dan jenis tersebut berisi deklarasi metode unik seperti itu, maka itu adalah metode dasar yang ditimpa.

Kami juga mengizinkan override properti dan pengindeks dalam antarmuka seperti yang ditentukan untuk kelas di §15.7.6 Pengakses virtual, disegel, diambil alih, dan abstrak.

Pencarian Nama

Pencarian nama di dalam deklarasi kelas override saat ini memengaruhi hasil pencarian nama dengan menerapkan detail anggota yang ditemukan dari deklarasi override yang paling terderivasi dalam hierarki kelas, dimulai dari jenis pengenal kualifikator (atau this jika tidak ada kualifikator). Misalnya, dalam §12.6.2.2 Parameter yang Sesuai, kami memiliki

Untuk metode virtual dan pengindeks yang ditentukan dalam kelas, daftar parameter dipilih dari deklarasi pertama atau penggantian anggota fungsi yang ditemukan saat dimulai dari tipe statis penerima, dan mencari melalui kelas dasarnya.

ke ini kita tambahkan

Untuk metode virtual dan pengindeks yang ditentukan dalam antarmuka, daftar parameter diambil dari deklarasi atau penimpaan anggota fungsi yang ditemukan dalam turunan paling akhir di antara tipe yang berisi deklarasi penggantian anggota fungsi. Ini adalah kesalahan waktu kompilasi jika tidak ada jenis unik seperti itu.

Untuk tipe hasil akses properti atau pengindeks, teks yang sudah ada

  • Jika I mengidentifikasi properti instans, hasilnya adalah akses properti dengan ekspresi instans yang terkait E dan jenis yang sesuai dengan jenis properti tersebut. Jika T adalah tipe kelas, tipe terkait dipilih dari deklarasi pertama atau penggantian properti yang ditemukan dengan dimulai dari T, dan mencari melalui kelas dasarnya.

dilengkapi dengan

Jika T adalah jenis antarmuka, jenis terkait dipilih dari deklarasi atau penimpaan properti yang ditemukan di yang paling berasal dari T atau antarmuka dasar langsung atau tidak langsungnya. Ini adalah kesalahan waktu kompilasi jika tidak ada jenis unik seperti itu.

Perubahan serupa harus dilakukan dalam akses pengindeks §12.8.12.3

Dalam §12.8.10 Ekspresi Pemanggilan, kami memperkaya teks yang ada.

  • Selain itu, hasilnya adalah nilai dengan tipe yang sama dengan tipe pengembalian metode atau delegasi. Jika pemanggilan adalah metode instans, dan penerima adalah jenis kelas T, jenis terkait dipilih dari deklarasi pertama atau penimpaan metode yang ditemukan saat dimulai dengan T dan mencari melalui kelas dasarnya.

dengan

Jika pemanggilan adalah metode instans, dan penerima merupakan tipe antarmuka T, tipe terkait dipilih dari deklarasi atau penimpaan metode yang ditemukan pada antarmuka yang paling diturunkan di antara T dan antarmuka dasarnya baik yang langsung maupun tidak langsung. Ini adalah kesalahan waktu kompilasi jika tidak ada jenis unik seperti itu.

Implementasi Antarmuka Implisit

Bagian spesifikasi ini

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

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

dimodifikasi sebagai berikut:

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

  • A dan B adalah metode, dan nama dan daftar parameter formal A dan B identik, dan jenis pengembalian A dapat dikonversi ke jenis pengembalian B melalui identitas konversi referensi implisit ke jenis pengembalian B.
  • A dan B adalah properti, nama A dan B identik, A memiliki aksesor yang sama dengan B (A diizinkan untuk memiliki aksesor tambahan jika bukan implementasi anggota antarmuka eksplisit), dan jenis A dapat dikonversi ke jenis pengembalian B melalui konversi identitas atau, jika A adalah properti baca-saja, konversi referensi implisit.
  • A dan B adalah peristiwa, dan nama dan jenis A dan B identik.
  • A dan B adalah pengindeks, daftar parameter formal A dan B identik, A memiliki aksesor yang sama dengan B (A diizinkan untuk memiliki aksesor tambahan jika bukan implementasi anggota antarmuka eksplisit), dan jenis A dapat dikonversi ke jenis pengembalian B melalui konversi identitas atau, jika A adalah pengindeks baca-saja, konversi referensi implisit.

Ini secara teknis merupakan perubahan yang mendasar, karena program di bawah ini mencetak "C1.M" hari ini, tapi akan mencetak "C2.M" di bawah usulan revisi.

using System;

interface I1 { object M(); }
class C1 : I1 { public object M() { return "C1.M"; } }
class C2 : C1, I1 { public new string M() { return "C2.M"; } }
class Program
{
    static void Main()
    {
        I1 i = new C2();
        Console.WriteLine(i.M());
    }
}

Karena perubahan mendasar ini, kami mungkin mempertimbangkan untuk tidak mendukung jenis pengembalian kovarian pada implementasi implisit.

Batasan pada Implementasi Antarmuka

Kami akan memerlukan aturan yang mengharuskan implementasi antarmuka eksplisit untuk mendeklarasikan jenis pengembalian yang tidak kurang turunan dari jenis pengembalian yang dinyatakan dalam penggantian apa pun di antarmuka dasarnya.

Implikasi Kompatibilitas API

TBD

Masalah Terbuka

Spesifikasi tidak mengatakan bagaimana pemanggil mendapatkan tipe pengembalian yang lebih rinci. Mungkin itu akan dilakukan dengan cara yang mirip dengan cara pemanggil mendapatkan spesifikasi parameter penimpaan yang paling turunan.


Jika kita memiliki antarmuka berikut:

interface I1 { I1 M(); }
interface I2 { I2 M(); }
interface I3: I1, I2 { override I3 M(); }

Perhatikan bahwa dalam I3, metode I1.M() dan I2.M() telah "digabungkan". Saat menerapkan I3, perlu mengimplementasikan keduanya bersama-sama.

Umumnya, kami memerlukan implementasi eksplisit untuk merujuk ke metode asli. Pertanyaannya adalah, di kelas

class C : I1, I2, I3
{
    C IN.M();
}

Apa artinya di sini? Bagaimana seharusnya N?

Saya menyarankan agar kami mengizinkan penerapan baik I1.M atau I2.M (tetapi tidak keduanya), dan memperlakukannya sebagai implementasi keduanya.

Kekurangan

  • [ ] Setiap perubahan bahasa harus memiliki manfaatnya sendiri.
  • [ ] Kita harus memastikan bahwa kinerjanya wajar, bahkan dalam kasus hierarki warisan mendalam
  • [ ] Kita harus memastikan bahwa artefak strategi terjemahan tidak memengaruhi semantik bahasa, bahkan ketika mengkonsumsi IL baru dari kompilator lama.

Alternatif

Kita dapat sedikit melonggarkan aturan bahasa agar memungkinkan dalam konteks sumber.

// Possible alternative. This was not implemented.
abstract class Cloneable
{
    public abstract Cloneable Clone();
}

class Digit : Cloneable
{
    public override Cloneable Clone()
    {
        return this.Clone();
    }

    public new Digit Clone() // Error: 'Digit' already defines a member called 'Clone' with the same parameter types
    {
        return this;
    }
}

Pertanyaan yang belum terselesaikan

  • [ ] Bagaimana API yang telah dikompilasi untuk menggunakan fitur ini berfungsi dalam versi bahasa yang lebih lama?

Rapat desain