Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
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 tertuangkan dalam catatan rapat terkait desain bahasa (LDM) .
Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .
Edisi unggulan: https://github.com/dotnet/csharplang/issues/8714
Ringkasan
Kami memperkenalkan dukungan kelas satu untuk Span<T> dan ReadOnlySpan<T> dalam bahasa, termasuk jenis konversi implisit baru dan mempertimbangkannya di lebih banyak tempat, memungkinkan pemrograman yang lebih alami dengan jenis integral ini.
Motivasi
Sejak diperkenalkan di C# 7.2, Span<T> dan ReadOnlySpan<T> telah diintegrasikan ke dalam bahasa dan pustaka kelas dasar (BCL) dalam banyak cara penting. Ini bagus untuk pengembang, karena pengenalan mereka meningkatkan performa tanpa membebani keselamatan pengembang. Namun, bahasa ini telah menjaga jenis-jenis ini pada jarak tertentu dalam beberapa cara utama, yang membuatnya sulit untuk mengekspresikan maksud API dan menyebabkan terjadinya banyak duplikasi pada API baru. Misalnya, BCL telah menambahkan sejumlah API primitif tensor baru di .NET 9, tetapi API ini semuanya ditawarkan pada ReadOnlySpan<T>. C# tidak mengenali hubungan antara ReadOnlySpan<T>, Span<T>, dan T[], jadi meskipun ada konversi yang ditentukan pengguna antara jenis ini, mereka tidak dapat digunakan untuk penerima metode ekstensi, tidak dapat menyusun dengan konversi lain yang ditentukan pengguna, dan tidak membantu dengan semua skenario inferensi jenis generik.
Pengguna perlu menggunakan konversi eksplisit atau argumen tipe, ini berarti bahwa alat IDE tidak memandu pengguna untuk menggunakan API-API ini, karena tidak ada yang menunjukkan kepada IDE bahwa valid untuk meneruskan tipe-tipe ini setelah dikonversi. Untuk memberikan kegunaan maksimum untuk gaya API ini, BCL harus menentukan seluruh set Span<T> dan T[] overload, yang membuat banyak area permukaan duplikat yang harus dipertahankan tanpa keuntungan nyata. Proposal ini berusaha mengatasi masalah dengan meminta bahasa lebih langsung mengenali jenis dan konversi ini.
Misalnya, BCL hanya dapat menambahkan satu kelebihan beban pembantu MemoryExtensions seperti:
int[] arr = [1, 2, 3];
Console.WriteLine(
arr.StartsWith(1) // CS8773 in C# 13, permitted with this proposal
);
public static class MemoryExtensions
{
public static bool StartsWith<T>(this ReadOnlySpan<T> span, T value) where T : IEquatable<T> => span.Length != 0 && EqualityComparer<T>.Default.Equals(span[0], value);
}
Sebelumnya, overload untuk Span dan array diperlukan untuk membuat metode ekstensi dapat digunakan pada variabel bertipe Span/array karena konversi yang ditentukan oleh pengguna (yang ada antara Span/array/ReadOnlySpan) tidak dipertimbangkan untuk penerima ekstensi.
Desain Terperinci
Perubahan dalam proposal ini akan terkait dengan LangVersion >= 14.
Konversi jangkauan
Kami menambahkan jenis konversi implisit baru ke daftar dalam §10.2.1, konversi rentang implisit . Konversi ini adalah konversi dari jenis dan didefinisikan sebagai berikut:
Konversi rentang implisit memungkinkan array_types, System.Span<T>, System.ReadOnlySpan<T>, dan string untuk dikonversi satu sama lain sebagai berikut:
- Dari dimensi tunggal mana pun
array_typeberjenis elemenEihinggaSystem.Span<Ei> - Dari
array_typeberdimensi tunggal mana pun dengan jenis elemenEikeSystem.ReadOnlySpan<Ui>, asalkanEidapat dikonversi secara kovarians (§18.2.3.3) keUi. - Dari
System.Span<Ti>keSystem.ReadOnlySpan<Ui>, dengan syarat kovariansTidapat dikonversi (§18.2.3.3) keUi - Dari
System.ReadOnlySpan<Ti>keSystem.ReadOnlySpan<Ui>, dengan syarat kovariansTidapat dikonversi (§18.2.3.3) keUi - Dari
stringkeSystem.ReadOnlySpan<char>
Setiap jenis Span/ReadOnlySpan dianggap berlaku untuk konversi jika mereka adalah ref structdan mereka cocok dengan nama lengkap (LDM 2024-06-24).
Kami juga menambahkan konversi rentang implisit ke daftar konversi implisit standar (§10,4,2). Ini memungkinkan resolusi kelebihan beban untuk mempertimbangkannya saat melakukan resolusi argumen, seperti dalam proposal API yang ditautkan sebelumnya.
Konversi rentang eksplisit adalah sebagai berikut:
- Semua konversi rentang implisit .
- Dari array_type dengan tipe elemen
TikeSystem.Span<Ui>atauSystem.ReadOnlySpan<Ui>dengan syarat terdapat konversi referensi eksplisit dariTikeUi.
Tidak ada konversi rentang eksplisit standar, tidak seperti konversi eksplisit standar lain (§10.4.3) yang selalu ada dengan adanya konversi implisit standar yang berlawanan.
Konversi yang ditentukan pengguna
Konversi yang ditentukan pengguna tidak dipertimbangkan saat mengonversi antara tipe yang memiliki konversi span implisit atau eksplisit.
Konversi rentang implisit dikecualikan dari aturan bahwa tidak dimungkinkan untuk menentukan operator yang didefinisikan oleh pengguna di antara jenis di mana konversi yang bukan didefinisikan oleh pengguna ada (§10.5.2 Konversi yang didefinisikan oleh pengguna yang diizinkan). Ini diperlukan agar BCL dapat terus mendefinisikan operator konversi Span yang ada bahkan ketika mereka beralih ke C# 14 (mereka masih diperlukan untuk LangVersions yang lebih rendah dan juga karena operator ini digunakan dalam codegen dari konversi rentang standar baru). Namun, ini dapat dilihat sebagai detail implementasi (codegen dan LangVersions yang lebih rendah bukan bagian dari spesifikasi), dan Roslyn melanggar bagian dari spesifikasi ini, karena aturan yang spesifik ini tentang konversi yang ditentukan pengguna tidak diberlakukan.
Penerima ekstensi
Kami juga menambahkan konversi rentang implisit ke daftar konversi implisit yang dapat diterima pada parameter pertama metode ekstensi saat menentukan penerapan (12.8.9.3) (perubahan tebal):
Metode ekstensi
Cᵢ.Mₑmemenuhi syarat jika:
Cᵢadalah kelas non-generik dan tidak bersarang- Nama
Mₑadalah pengidentifikasiMₑdapat diakses dan berlaku saat diterapkan ke argumen sebagai metode statis seperti yang ditunjukkan di atas- Identitas implisit, referensi
atau tinju, tinju, atau konversi rentang ada dari expr ke tipe parameter pertama dariMₑ. Konversi rentang tidak dipertimbangkan pada saat resolusi kelebihan beban dilakukan untuk sebuah konversi grup metode.
Perhatikan bahwa konversi rentang implisit tidak dipertimbangkan untuk penerima ekstensi dalam konversi grup metode (LDM 2024-07-15) yang membuat kode berikut terus berfungsi dibandingkan dengan mengakibatkan kesalahan waktu kompilasi CS1113: Extension method 'E.M<int>(Span<int>, int)' defined on value type 'Span<int>' cannot be used to create delegates:
using System;
using System.Collections.Generic;
Action<int> a = new int[0].M; // binds to M<int>(IEnumerable<int>, int)
static class E
{
public static void M<T>(this Span<T> s, T x) => Console.Write(1);
public static void M<T>(this IEnumerable<T> e, T x) => Console.Write(2);
}
Sebagai kemungkinan pekerjaan di masa depan, kita dapat mempertimbangkan untuk menghapus kondisi ini, yaitu bahwa konversi rentang tidak dipertimbangkan untuk penerima ekstensi dalam konversi kelompok metode, dan sebaliknya menerapkan perubahan sehingga skenario seperti di atas akan berhasil memanggil kelebihan beban Span sebagai hasilnya.
- Kompiler dapat menghasilkan sebuah thunk yang akan mengambil array sebagai penerima dan melakukan konversi rentang di dalamnya (mirip dengan pengguna yang membuat delegasi secara manual seperti
x => new int[0].M(x)). - Delegasi nilai apabila diimplementasikan bisa menerima
Spansecara langsung sebagai penerima.
Variansi
Tujuan dari bagian varians dalam konversi rentang implisit adalah untuk mereplikasi sejumlah kovariansi untuk System.ReadOnlySpan<T>. Perubahan runtime akan diperlukan untuk mengimplementasikan varians sepenuhnya melalui generik di sini (lihat .. /csharp-13.0/ref-struct-interfaces.md untuk menggunakan ref struct jenis dalam generik), tetapi kita dapat mengizinkan kovariansi dalam jumlah terbatas melalui penggunaan API .NET 9 yang diusulkan: https://github.com/dotnet/runtime/issues/96952. Ini akan memungkinkan bahasa memperlakukan System.ReadOnlySpan<T> seolah-olah T dinyatakan sebagai out T dalam beberapa skenario. Namun, kami tidak menelusuri konversi varian ini melalui semua skenario varians, dan tidak menambahkannya ke definisi varians-konversibel dalam §18.2.3.3. Jika di masa depan, kami mengubah runtime untuk lebih memahami variansi di sini, kita dapat mengambil perubahan kecil untuk sepenuhnya mengenalinya dalam bahasa.
Pola
Perhatikan bahwa ketika ref structdigunakan sebagai jenis dalam pola apa pun, hanya konversi identitas yang diizinkan:
class C<T> where T : allows ref struct
{
void M1(T t) { if (t is T x) { } } // ok (T is T)
void M2(R r) { if (r is R x) { } } // ok (R is R)
void M3(T t) { if (t is R x) { } } // error (T is R)
void M4(R r) { if (r is T x) { } } // error (R is T)
}
ref struct R { }
Dari spesifikasi , operator tipe (§12.12.12.1):
Hasil operasi
E is T[...] adalah nilai Boolean yang menunjukkan apakahEnon-null dan dapat berhasil dikonversi ke jenisTmelalui konversi referensi, konversi pembungkusan, konversi pembukaan kotak, konversi pembungkusan, atau konversi pembukaan bungkus.[...]
Jika
Tadalah jenis nilai yang tidak dapat diubah ke null, hasilnyatruejikaDdanTadalah jenis yang sama.
Perilaku ini tidak berubah dengan fitur ini, oleh karena itu tidak akan mungkin untuk menulis pola untuk Span/ReadOnlySpan, meskipun pola serupa dimungkinkan untuk array (termasuk varians):
using System;
M1<object[]>(["0"]); // prints
M1<string[]>(["1"]); // prints
void M1<T>(T t)
{
if (t is object[] r) Console.WriteLine(r[0]); // ok
}
void M2<T>(T t) where T : allows ref struct
{
if (t is ReadOnlySpan<object> r) Console.WriteLine(r[0]); // error
}
Pembuatan kode
Konversi akan selalu ada, terlepas dari apakah ada pembantu runtime yang digunakan untuk mengimplementasikannya ada (LDM 2024-05-13). Jika penolong tidak ada, upaya menggunakan konversi akan menghasilkan kesalahan saat kompilasi karena adanya anggota yang dibutuhkan kompilator yang tidak ditemukan.
Pengkompilasi mengharapkan untuk menggunakan pembantu atau setara berikut untuk mengimplementasikan konversi:
| Konversi | Pembantu |
|---|---|
| array menjadi rentang |
static implicit operator Span<T>(T[]) (ditentukan dalam Span<T>) |
| array ke ReadOnlySpan |
static implicit operator ReadOnlySpan<T>(T[]) (ditentukan dalam ReadOnlySpan<T>) |
| Rentang ke ReadOnlySpan |
static implicit operator ReadOnlySpan<T>(Span<T>) (didefinisikan dalam Span<T>) dan static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| ReadOnlySpan ke ReadOnlySpan | static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| string ke ReadOnlySpan | static ReadOnlySpan<char> MemoryExtensions.AsSpan(string) |
Perhatikan bahwa MemoryExtensions.AsSpan digunakan alih-alih operator implisit yang setara yang ditentukan pada string.
Ini berarti codegen berbeda antara LangVersions (operator implisit digunakan dalam C# 13; metode statis AsSpan digunakan dalam C# 14).
Di sisi lain, konversi bisa dilakukan pada .NET Framework (metode AsSpan ada sedangkan operator string tidak).
Konversi array eksplisit ke (ReadOnly)Span pertama-tama secara eksplisit mengonversi dari array sumber ke array dengan jenis elemen tujuan, lalu ke (ReadOnly)Span menggunakan pembantu yang sama dengan yang digunakan pada konversi implisit, yaitu, op_Implicit(T[])yang sesuai.
Konversi yang lebih baik dari ekspresi
Konversi dari ekspresi (§12.6.4.5) diperbarui untuk lebih mengutamakan konversi implisit rentang. Ini didasarkan pada ekspresi koleksi perubahan resolusi kelebihan beban.
Mengingat
C₁konversi implisit yang mengonversi dari ekspresiEmenjadi jenisT₁, danC₂konversi implisit yang mengonversi dari ekspresiEmenjadi jenisT₂,C₁adalah konversi yang lebih baik daripadaC₂jika salah satu penangguhan berikut:
Eadalah ekspresi koleksi, danC₁adalah konversi koleksi lebih baik dari ekspresi daripadaC₂Ebukan ekspresi koleksi dan salah satu kondisi berikut:
Esama persis denganT₁danEtidak sama persis denganT₂Etidak persis sama denganT₁danT₂, danC₁adalah konversi rentang implisit sedangkanC₂bukan konversi rentang implisit.Etepat cocok dengan keduanya atau tidak satupun dariT₁danT₂, keduanya atau tidak satupun dariC₁danC₂adalah konversi rentang implisit, danT₁lebih baik sebagai target konversi daripadaT₂Eadalah grup metode,T₁kompatibel dengan metode terbaik tunggal dari grup metode untuk konversiC₁, danT₂tidak kompatibel dengan metode terbaik tunggal dari grup metode untuk konversiC₂
Target konversi yang lebih baik
Sasaran konversi yang lebih baik (§12.6.4.7) diperbarui agar lebih memilih ReadOnlySpan<T> daripada Span<T>.
Mengingat dua jenis
T₁danT₂,T₁adalah target konversi yang lebih baik daripadaT₂jika salah satu dari kondisi berikut berlaku:
T₁System.ReadOnlySpan<E₁>,T₂System.Span<E₂>, dan terdapat konversi identik dariE₁keE₂T₁System.ReadOnlySpan<E₁>,T₂System.ReadOnlySpan<E₂>, dan konversi implisit dariT₁keT₂ada dan tidak ada konversi implisit dariT₂keT₁ada- Setidaknya salah satu
T₁atauT₂tidakSystem.ReadOnlySpan<Eᵢ>dan tidakSystem.Span<Eᵢ>, dan konversi implisit dariT₁keT₂ada dan tidak ada konversi implisit dariT₂keT₁ada- ...
Rapat desain:
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#preferring-readonlyspant-over-spant-conversions
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-09.md#first-class-span-open-questions
Komentar perbaikan
konversi aturan yang lebih baik dari ekspresi harus memastikan bahwa setiap kali kelebihan beban berlaku karena konversi rentang baru, potensi ambiguitas dengan kelebihan beban lainnya dapat dihindari karena kelebihan beban yang baru berlaku lebih diprioritaskan.
Tanpa aturan ini, kode berikut yang berhasil dikompilasi dalam C# 13 akan mengakibatkan kesalahan ambiguitas di C# 14 karena konversi implisit standar baru dari array ke ReadOnlySpan yang berlaku untuk penerima metode ekstensi:
using System;
using System.Collections.Generic;
var a = new int[] { 1, 2, 3 };
a.M();
static class E
{
public static void M(this IEnumerable<int> x) { }
public static void M(this ReadOnlySpan<int> x) { }
}
Aturan ini juga memungkinkan pengenalan API baru yang sebelumnya akan menghasilkan ambiguitas, misalnya:
using System;
using System.Collections.Generic;
C.M(new int[] { 1, 2, 3 }); // would be ambiguous before
static class C
{
public static void M(IEnumerable<int> x) { }
public static void M(ReadOnlySpan<int> x) { } // can be added now
}
Peringatan
Karena aturan kesempurnaan didefinisikan untuk konversi rentang yang hanya ada di LangVersion >= 14, penulis API tidak dapat menambahkan kelebihan beban baru tersebut jika mereka ingin tetap mendukung pengguna di LangVersion <= 13.
Misalnya, jika .NET 9 BCL memperkenalkan kelebihan beban seperti itu, pengguna yang meningkatkan ke net9.0 TFM tetapi tetap berada di LangVersion yang lebih rendah akan mendapatkan kesalahan ambiguitas untuk kode yang ada.
Lihat juga pertanyaan terbuka di bawah ini.
Inferensi jenis
Kami memperbarui bagian penarikan jenis dari spesifikasi sebagai berikut (perubahan dalam bold).
12.6.3.9 Inferensi yang tepat
Inferensi yang tepat dari tipe ke tipe
Udibuat sebagai berikut:
- Jika
Vadalah salah satu dariXᵢyang belum diperbaiki, makaUditambahkan ke dalam kumpulan batas yang tepat untukXᵢ.- Jika tidak, set
V₁...VₑdanU₁...Uₑditentukan dengan memeriksa apakah salah satu kasus berikut berlaku:
Vadalah jenis arrayV₁[...]danUadalah jenis arrayU₁[...]dengan peringkat yang samaVadalahSpan<V₁>danUadalah jenis arrayU₁[]atauSpan<U₁>VadalahReadOnlySpan<V₁>danUadalah jenis arrayU₁[]atauSpan<U₁>atauReadOnlySpan<U₁>Vadalah jenisV₁?danUadalah jenisU₁Vadalah jenis yang dibentukC<V₁...Vₑ>danUadalah jenis yang dibentukC<U₁...Uₑ>
Jika salah satu kasus ini berlaku, maka inferensi persis dapat dibuat dari setiapUᵢkeVᵢyang sesuai.- Jika tidak, tidak ada inferensi yang dibuat.
12.6.3.10 Inferensi batas bawah
Inferensi batas bawah dari jenis
Uke jenisVdibuat sebagai berikut:
- Jika
Vadalah salah satuXᵢyang belum diperbaiki makaUditambahkan ke set batas bawah untukXᵢ.- Jika tidak, jika
Vadalah jenisV₁?danUadalah jenisU₁?maka inferensi terikat yang lebih rendah dibuat dariU₁keV₁.- Jika tidak, set
U₁...UₑdanV₁...Vₑditentukan dengan memeriksa apakah salah satu kasus berikut berlaku:
Vadalah jenis arrayV₁[...]danUadalah jenis arrayU₁[...]dari peringkat yang samaVadalahSpan<V₁>danUadalah jenis arrayU₁[]atauSpan<U₁>VadalahReadOnlySpan<V₁>danUadalah jenis arrayU₁[]atauSpan<U₁>atauReadOnlySpan<U₁>Vadalah salah satu dariIEnumerable<V₁>,ICollection<V₁>,IReadOnlyList<V₁>>,IReadOnlyCollection<V₁>, atauIList<V₁>, danUadalah jenis array satu dimensiU₁[]Vadalahclassjenisstruct,interface,delegate, atauC<V₁...Vₑ>yang dibangun dan ada jenis unikC<U₁...Uₑ>yang membuatU(atau, jikaUadalah jenisparameter, kelas dasar efektifnya atau anggota set antarmuka efektifnya) identik dengan,inheritsdari (secara langsung atau tidak langsung), atau mengimplementasikan (secara langsung atau tidak langsung)C<U₁...Uₑ>.- (Pembatasan "keunikan" berarti bahwa dalam kasus antarmuka
C<T>{} class U: C<X>, C<Y>{}, maka tidak ada inferensi yang dibuat ketika menyimpulkan dariUkeC<T>karenaU₁bisaXatauY.)
Jika salah satu kasus ini berlaku, inferensi dibuat dari setiapUᵢkeVᵢyang sesuai sebagai berikut:- Jika
Uᵢtidak diketahui sebagai jenis referensi, maka dibuat inferensi yang tepat- Jika tidak, jika
Uadalah tipe array, makainferensi batas bawah dibuatinferensi tergantung pada jenisV:
- Jika
VadalahSpan<Vᵢ>, maka inferensi yang tepat dibuat- Jika
Vadalah jenis array atauReadOnlySpan<Vᵢ>, maka inferensi batas bawah dilakukan- Jika tidak, jika
UadalahSpan<Uᵢ>maka inferensi tergantung pada jenisV:
- Jika
VadalahSpan<Vᵢ>, maka inferensi yang tepat dibuat- Jika
VadalahReadOnlySpan<Vᵢ>, maka inferensi batas bawah dibuat- Jika tidak, jika
UadalahReadOnlySpan<Uᵢ>danVadalahReadOnlySpan<Vᵢ>inferensi yang terikat lebih rendah dibuat:- Jika tidak, jika
VadalahC<V₁...Vₑ>maka inferensi bergantung pada parameter tipei-thdariC:
- Jika itu kovarian maka dibuat inferensi terikat bawah .
- Jika kontravarian maka inferensi batas atas dibuat.
- Jika bersifat invarian maka inferensi yang tepat dibuat.
- Jika tidak, tidak ada inferensi yang dibuat.
Tidak ada aturan untuk inferensi batas atas karena tidak mungkin untuk mencapainya.
Inferensi jenis tidak pernah dimulai dari batas atas, itu harus melalui inferensi batas bawah dan parameter jenis kontravarian.
Karena aturan "jika Uᵢ tidak diketahui sebagai tipe referensi maka sebuah inferensi yang tepat dibuat," argumen tipe sumber tidak dapat Span/ReadOnlySpan (tidak dapat menjadi tipe referensi).
Namun, inferensi rentang batas atas hanya akan berlaku jika jenis sumber adalah Span/ReadOnlySpan, karena akan memiliki aturan seperti:
UadalahSpan<U₁>danVadalah jenis arrayV₁[]atauSpan<V₁>UadalahReadOnlySpan<U₁>danVadalah jenis arrayV₁[]atauSpan<V₁>atauReadOnlySpan<V₁>
Perubahan mendasar
Seperti halnya proposal lain yang mengubah konversi skenario yang ada, proposal ini memang memperkenalkan beberapa perubahan signifikan baru. Berikut adalah beberapa contoh:
Memanggil Reverse pada array
Memanggil x.Reverse() di mana x adalah instans jenis T[] sebelumnya akan mengikat ke IEnumerable<T> Enumerable.Reverse<T>(this IEnumerable<T>), sedangkan sekarang mengikat ke void MemoryExtensions.Reverse<T>(this Span<T>).
Sayangnya API-API ini tidak kompatibel (API yang terakhir melakukan pembalikan di tempat dan mengembalikan void).
.NET 10 mengurangi ini dengan menambahkan kelebihan beban khusus array IEnumerable<T> Reverse<T>(this T[]), lihat https://github.com/dotnet/runtime/issues/107723.
void M(int[] a)
{
foreach (var x in a.Reverse()) { } // fine previously, an error now (`Reverse` returns `void`)
foreach (var x in Enumerable.Reverse(a)) { } // workaround
}
Lihat juga:
- https://developercommunity.visualstudio.com/t/Extension-method-SystemLinqEnumerable/10790323
- https://developercommunity.visualstudio.com/t/Compilation-Error-When-Calling-Reverse/10818048
- https://developercommunity.visualstudio.com/t/Version-17131-has-an-obvious-defect-th/10858254
- https://developercommunity.visualstudio.com/t/Visual-Studio-2022-update-breaks-build-w/10856758
- https://github.com/dotnet/runtime/issues/111532
- https://developercommunity.visualstudio.com/t/Backward-compatibility-issue-:-IEnumerab/10896189#T-ND10896782
Rapat desain: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#reverse
Ambiguitas
Contoh berikut sebelumnya gagal penyimpulan tipe untuk overload Span, tetapi sekarang penyimpulan tipe dari array ke Span berhasil, sehingga hasilnya menjadi ambigu.
Untuk mengatasi hal ini, pengguna dapat menggunakan .AsSpan() atau penulis API dapat menggunakan OverloadResolutionPriorityAttribute.
var x = new long[] { 1 };
Assert.Equal([2], x); // previously Assert.Equal<T>(T[], T[]), now ambiguous with Assert.Equal<T>(ReadOnlySpan<T>, Span<T>)
Assert.Equal([2], x.AsSpan()); // workaround
var x = new int[] { 1, 2 };
var s = new ArraySegment<int>(x, 1, 1);
Assert.Equal(x, s); // previously Assert.Equal<T>(T, T), now ambiguous with Assert.Equal<T>(Span<T>, Span<T>)
Assert.Equal(x.AsSpan(), s); // workaround
xUnit menambahkan lebih banyak kelebihan beban untuk mengurangi hal ini: https://github.com/xunit/xunit/discussions/3021.
Rapat desain: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#new-ambiguities
Array kovarian
Overload yang menggunakan IEnumerable<T> berfungsi pada array kovarian, tetapi overload yang menggunakan Span<T> (yang sekarang lebih kita sukai) tidak, karena konversi rentang menghasilkan ArrayTypeMismatchException untuk array kovarian.
Bisa dibilang, pemakaian yang berlebihan dari Span<T> sebaiknya tidak ada, sebaiknya gunakan ReadOnlySpan<T> sebagai gantinya.
Untuk mengatasi hal ini, pengguna dapat menggunakan .AsEnumerable(), atau penulis API dapat menggunakan OverloadResolutionPriorityAttribute atau menambahkan kelebihan beban ReadOnlySpan<T> yang lebih disukai karena aturan kesempurnaan.
string[] s = new[] { "a" };
object[] o = s;
C.R(o); // wrote 1 previously, now crashes in Span<T> constructor with ArrayTypeMismatchException
C.R(o.AsEnumerable()); // workaround
static class C
{
public static void R<T>(IEnumerable<T> e) => Console.Write(1);
public static void R<T>(Span<T> s) => Console.Write(2);
// another workaround:
public static void R<T>(ReadOnlySpan<T> s) => Console.Write(3);
}
Rapat desain: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#covariant-arrays
Memilih ReadOnlySpan daripada Span
Aturan keunggulan menyebabkan preferensi terhadap overload ReadOnlySpan dibandingkan Span untuk menghindari ArrayTypeMismatchExceptiondalam skenario array kovarian seperti .
Hal ini dapat menyebabkan kompilasi terputus dalam beberapa skenario, misalnya ketika kelebihan beban berbeda dengan jenis pengembaliannya:
double[] x = new double[0];
Span<ulong> y = MemoryMarshal.Cast<double, ulong>(x); // previously worked, now a compilation error (returns ReadOnlySpan, not Span)
Span<ulong> z = MemoryMarshal.Cast<double, ulong>(x.AsSpan()); // workaround
static class MemoryMarshal
{
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> span) => default;
public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> span) => default;
}
Lihat https://github.com/dotnet/roslyn/issues/76443.
Pohon Ekspresi
Overload yang menggunakan rentang seperti MemoryExtensions.Contains lebih disukai daripada overload klasik seperti Enumerable.Contains, bahkan di dalam pohon ekspresi - tetapi struct ref tidak didukung oleh mesin interpreter.
Expression<Func<int[], int, bool>> exp = (array, num) => array.Contains(num);
exp.Compile(preferInterpretation: true); // fails at runtime in C# 14
Expression<Func<int[], int, bool>> exp2 = (array, num) => Enumerable.Contains(array, num); // workaround
exp2.Compile(preferInterpretation: true); // ok
Demikian pula, mesin terjemahan seperti LINQ-ke-SQL perlu bereaksi terhadap ini jika pengunjung pohon mereka mengharapkan Enumerable.Contains karena mereka akan bertemu MemoryExtensions.Contains sebagai gantinya.
Lihat juga:
- https://github.com/dotnet/runtime/issues/109757
- https://github.com/dotnet/docs/issues/43952
- https://github.com/dotnet/efcore/issues/35100
- https://github.com/dotnet/csharplang/discussions/8959
Rapat desain:
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#conversions-in-expression-trees
- https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-06.md#ignoring-ref-structs-in-expressions
Konversi yang ditentukan pengguna melalui pewarisan
Dengan menambahkan konversi rentang implisit ke daftar konversi implisit standar, kita berpotensi mengubah perilaku saat konversi yang ditentukan pengguna terlibat dalam hierarki jenis. Contoh ini menunjukkan bagaimana perubahan dibandingkan dengan skenario angka integer yang sudah berperilaku seperti yang akan dilakukan oleh perilaku baru C# 14.
Span<string> span = [];
var d = new Derived();
d.M(span); // Base today, Derived tomorrow
int i = 1;
d.M(i); // Derived today, demonstrates new behavior
class Base
{
public void M(Span<string> s)
{
Console.WriteLine("Base");
}
public void M(int i)
{
Console.WriteLine("Base");
}
}
class Derived : Base
{
public static implicit operator Derived(ReadOnlySpan<string> r) => new Derived();
public static implicit operator Derived(long l) => new Derived();
public void M(Derived s)
{
Console.WriteLine("Derived");
}
}
Lihat juga: https://github.com/dotnet/roslyn/issues/78314
Metode pencarian ekstensi
Dengan mengizinkan konversi rentang implisit dalam pencarian metode ekstensi, kita berpotensi mengubah metode ekstensi apa yang diselesaikan dengan resolusi kelebihan beban.
namespace N1
{
using N2;
public class C
{
public static void M()
{
Span<string> span = new string[0];
span.Test(); // Prints N2 today, N1 tomorrow
}
}
public static class N1Ext
{
public static void Test(this ReadOnlySpan<string> span)
{
Console.WriteLine("N1");
}
}
}
namespace N2
{
public static class N2Ext
{
public static void Test(this Span<string> span)
{
Console.WriteLine("N2");
}
}
}
Buka pertanyaan
Aturan kesempurnaan yang tidak dibatasi
Haruskah kita membuat aturan peningkatan tidak bersyarat terhadap LangVersion? Itu akan memungkinkan penulis API untuk menambahkan API Span baru di mana ada padanan IEnumerable tanpa mengganggu pengguna pada versi bahasa yang lebih lama atau kompilator atau bahasa lain (misalnya, VB). Namun, itu berarti pengguna bisa mendapatkan perilaku yang berbeda setelah memperbarui toolset (tanpa mengubah LangVersion atau TargetFramework):
- Pengompilasi dapat memilih overload yang berbeda (secara teknis merupakan perubahan yang dapat menyebabkan kesalahan, tetapi diharapkan overload tersebut mempunyai perilaku yang setara).
- Jeda lainnya dapat muncul, belum diketahui saat ini.
Perhatikan bahwa OverloadResolutionPriorityAttribute tidak dapat sepenuhnya menyelesaikan ini karena juga diabaikan pada LangVersions yang lebih lama.
Namun, harus dimungkinkan untuk menggunakannya untuk menghindari ambiguitas dari VB di mana atribut harus dikenali.
Mengabaikan lebih banyak lagi konversi yang didefinisikan oleh pengguna
Kami mendefinisikan sekumpulan pasangan tipe untuk mana konversi rentang implisit dan eksplisit didefinisikan oleh bahasa.
Setiap kali konversi rentang yang ditentukan bahasa ada dari T1 ke T2, konversi yang ditentukan pengguna dari T1 ke T2diabaikan (terlepas dari rentang dan konversi yang ditentukan pengguna yang implisit atau eksplisit).
Perhatikan bahwa ini mencakup semua kondisi, jadi misalnya tidak ada konversi rentang dari Span<object> ke ReadOnlySpan<string> (ada konversi rentang dari Span<T> ke ReadOnlySpan<U> tetapi harus menahan T : U), oleh karena itu konversi yang ditentukan pengguna akan dipertimbangkan antara jenis tersebut jika ada (yang harus menjadi konversi khusus seperti Span<T> ke ReadOnlySpan<string> karena operator konversi tidak dapat memiliki parameter generik).
Haruskah kita mengabaikan konversi yang ditentukan oleh pengguna juga untuk kombinasi lain dari jenis array/Span/ReadOnlySpan/string di mana tidak ada konversi span yang ditentukan oleh bahasa yang tersedia?
Misalnya, jika ada konversi yang ditentukan pengguna dari ReadOnlySpan<T> ke Span<T>, haruskah kita mengabaikannya?
Opsi spesifikasi yang perlu dipertimbangkan
-
Setiap kali terdapat konversi rentang dari
T1keT2, abaikan konversi yang didefinisikan oleh pengguna dariT1keT2atau dariT2keT1. -
Konversi yang ditentukan pengguna tidak dipertimbangkan saat mengonversi antara
- dimensi tunggal
array_typedan setiapSystem.Span<T>/System.ReadOnlySpan<T>, - kombinasi dari
System.Span<T>/System.ReadOnlySpan<T>, -
stringdanSystem.ReadOnlySpan<char>.
- dimensi tunggal
- Seperti di atas tetapi mengganti poin terakhir dengan:
-
stringdanSystem.Span<char>/System.ReadOnlySpan<char>.
-
- Seperti di atas tetapi mengganti poin terakhir dengan:
-
stringdanSystem.Span<T>/System.ReadOnlySpan<T>.
-
Secara teknis, spesifikasi melarang beberapa konversi yang ditentukan pengguna ini untuk didefinisikan: tidak mungkin untuk menentukan operator yang ditentukan pengguna di antara jenis di mana konversi yang tidak ditentukan pengguna ada (§10.5.2).
Tapi Roslyn sengaja melanggar bagian spesifikasi ini. Dan beberapa konversi seperti antara Span dan string tetap diizinkan (tidak ada konversi yang ditentukan bahasa antara jenis ini).
Namun demikian, daripada hanya mengabaikan konversi, kita dapat melarang pendefinisian mereka sama sekali dan mungkin keluar dari pelanggaran spesifikasi, setidaknya untuk konversi rentang baru ini. Yaitu, mengubah Roslyn untuk benar-benar melaporkan kesalahan pada waktu kompilasi jika konversi ini didefinisikan (kemungkinan kecuali yang sudah ditentukan oleh BCL).
Alternatif
Pertahankan hal-hal apa adanya.
C# feature specifications