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.
Edisi unggulan: https://github.com/dotnet/csharplang/issues/9704
RINGKASAN
Kami memperbarui definisi unsafe dalam C# dari merujuk ke lokasi di mana jenis pointer digunakan, menjadi lokasi di mana memori yang tidak dikelola oleh runtime didereferensikan. Lokasi-lokasi ini adalah tempat memori tidak aman terjadi, dan bertanggung jawab atas sebagian besar CVE (Kerentanan Umum dan Paparan) dikategorikan sebagai masalah keamanan memori.
// Under the proposed rules:
void M()
{
int i = 1;
int* ptr = &i; // Allowed: creating a pointer is not itself unsafe
unsafe
{
Console.WriteLine(*ptr); // Dereference of memory not managed by the runtime. This is unsafe.
ref int intRef = Unsafe.AsRef(ptr); // Conversion of memory not managed by the runtime to a `ref`. This is unsafe.
}
}
namespace System.Runtime.CompilerServices
{
public static class Unsafe
{
unsafe public static ref T AsRef<T>(void* source) { /* ... */ } // `unsafe` marks the member as *requires-unsafe*.
}
}
Motivasi
Latar belakang untuk fitur ini juga dapat ditemukan di https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.md, yang melacak perubahan ekosistem yang lebih luas yang akan diperlukan sebagai bagian dari proposal ini.
Ini termasuk pembaruan BCL untuk menganotasi metode dengan benar sebagai tidak aman, serta pembaruan alat untuk pemahaman yang lebih baik tentang di mana memori tidak aman terjadi. Untuk C# secara khusus, kami ingin memastikan bahwa memori tidak aman dilacak dengan benar oleh bahasa; saat ini, mungkin sulit untuk melihat program secara holistik dan memahami semua lokasi di mana memori tidak aman terjadi. Ini karena berbagai pembantu System.Runtime.CompilerServices.Unsafeseperti , , System.Runtime.InteropServices.Marshaldan lainnya tidak menyatakan bahwa mereka melanggar keamanan memori dan membutuhkan pertimbangan khusus. Metode yang kemudian menggunakan pembantu ini tidak segera jelas, dan ketika mengaudit kode untuk masalah keamanan memori (baik sebelumnya saat melakukan peninjauan, atau ketika mencoba menentukan penyebab kerentanan yang sedang dilaporkan) mungkin sulit untuk menentukan lokasi yang dapat berkontribusi pada masalah.
Secara historis, unsafe dalam C# telah mengacu pada lubang keamanan memori tertentu: keberadaan jenis pointer. Saat jenis pointer tidak lagi terlibat, C# sangat senang membiarkan memori tidak aman terletak laten dalam kode. Masalah inilah yang ingin kami atasi dengan evolusi unsafe C# ini dan ekosistem .NET, area pelabelan di mana memori tidak aman berpotensi terjadi, sehingga memudahkan peninjau dan auditor untuk memahami batas-batas potensi memori yang tidak aman dalam suatu program. Yang penting, ini berarti bahwa kita akan mengubah arti , unsafebukan hanya menambahnya. Keberadaan pointer tidak aman; tindakan tidak aman mendereferensikan penunjuk. Ini meluas lebih jauh ke jenis diri mereka sendiri; tipe tidak boleh secara inheren tidak aman. Ini hanya tindakan menggunakan jenis yang bisa tidak aman, bukan keberadaan jenis itu.
Agar informasi ini mengalir melalui sistem, oleh karena itu kita harus memiliki cara untuk menandai metode itu sendiri sebagai unsafe. Saat ini, unsafe sebagai pengubah metode tidak memiliki dampak eksternal, itu hanya memungkinkan penunjuk digunakan dalam tanda tangan dan isi anggota. Ke depannya, unsafe sebagai pengubah benar-benar akan mengubah arti anggota secara publik; itu akan menunjukkan bahwa anggota memiliki masalah keamanan memori dan penggunaan apa pun harus divalidasi secara manual oleh programmer menggunakan anggota. Ini adalah perluasan dari arti yang ada dari unsafe: unsafe pada tubuh melokalisasi kewajiban audit untuk isi tersebut, sementara unsafe pada tanda tangan memperluas kewajiban itu kepada pemanggil.
Ini adalah perubahan yang berpotensi besar untuk segmen tertentu dari basis pengguna C#. Harapan kami adalah bahwa, bagi banyak pengguna kami, ini secara efektif transparan, dan memperbarui ke aturan baru akan mulus. Namun, mengingat bahwa beberapa permukaan API besar seperti bagian besar pantulan mungkin perlu ditandai , kami pikir kemungkinan perlu ada on-ramp yang layak untuk aturan baru untuk menghindari sepenuhnya mengobarkan unsafeekosistem.
Perubahan mendasar
Perubahan mencolok berikut dapat diamati saat memperbarui ke pengkompilasi yang mengimplementasikan fitur bahasa ini.
- Jika aturan keamanan memori yang diperbarui diaktifkan (yang mungkin merupakan opsi default atau bahkan satu-satunya opsi dalam versi .NET mendatang):
-
unsafepada anggota sekarang juga menandainya sebagai memerlukan tidak aman, yang berarti penelepon harus dalamunsafekonteks dan penimpaan tidak bolehunsafejika anggota dasar aman. -
unsafepada anggota atau jenis tidak secara otomatis memperkenalkanunsafekonteks, yang berarti blok eksplisitunsafeharus digunakan di sekitarunsafeoperasi di badan anggota dan penginisialisasi. -
externanggota dan bidang dalam tata letak eksplisit memerlukan eksplisitunsafe/safekata kunci pada deklarasi. -
stackallocdalam kondisi tertentu memerlukanunsafekonteks. -
unsafepengubah adalah kesalahan pada deklarasi jenis, konstruktor statis, dan destruktor, karena tidak memiliki efek apa pun.
-
- Di bawah langversi baru:
- Inferensi Lambda mungkin mempertimbangkan lebih banyak kandidat, yang mengakibatkan ambiguitas resolusi kelebihan beban.
-
safesekarang adalah kata kunci kontekstual yang mungkin merusak kode yang menggunakannya sebagai jenis. - Mode kompatiati di bawah mode warisan dapat mengakibatkan lebih banyak kesalahan.
Desain Terperinci
Terminologi: kami memanggil anggota memerlukan-tidak aman (sebelumnya dikenal sebagai penelepon-tidak aman) jika
- di bawah aturan keamanan memori yang diperbarui, mereka ditandai
unsafe, - di bawah aturan keamanan memori warisan , mereka berisi penunjuk dalam tanda tangan.
Syntax
Proposal ini memperkenalkan:
- kata kunci kontekstual baru
safe, terperinci di bawah ini, - dan
unsafeekspresi (unsafe(expression)), dirinci dalam ekspresi yang tidak aman.
Sintaks baru ini tersedia di bawah LangVersion baru, tetapi terlepas dari keikutsertaan, di bawah premis bahwa kami mencoba membuatnya sehingga apa pun yang harus Anda lakukan ketika Anda memilih ikut serta, Anda diizinkan untuk melakukannya sebelum Anda ikut serta.
safe Kata kunci
Pengubah safe dapat diterapkan ke semua deklarasi yang memungkinkan unsafe untuk menandainya sebagai memerlukan-tidak aman.
Tidak diizinkan untuk menerapkan safe pengubah dan unsafe pada deklarasi yang sama.
Pembatasan lain yang berlaku untuk unsafe dan safe pengubah ditentukan di bagian Pengubah dan konteks yang tidak aman.
Pengkompilasi memerlukan eksplisit safe atau unsafe pengubah pada extern anggota dan bidang dalam tata letak eksplisit.
safe Mengizinkan bahkan pada deklarasi di mana itu tidak diperlukan (dan karenanya tidak berpengaruh bagi kompilator) dimotivasi oleh generator sumber, misalnya, LibraryImport.
Pengubah safe hanya menandai deklarasi sebagai tidakmemerlukan-tidak aman, pengubah tidak memperkenalkan konteks yang aman.
Tidak ada safe juga formulir blok atau ekspresi.
Aturan yang sudah unsafe ada
Spesifikasi C# yang ada memiliki bagian besar yang dikhususkan untuk unsafe: §24 Kode tidak aman. Ini didefinisikan sebagai normatif kondisional, karena tidak diperlukan untuk pengkompilasi C# yang valid untuk mendukung fitur tersebut unsafe . Sebagian besar yang saat ini dianggap normatif kondisional tidak akan lagi demikian setelah perubahan ini, karena sebagian besar definisi pointer tidak lagi dianggap tidak aman dalam dirinya sendiri.
Jenis penunjuk, variabel Tetap dan dapat dipindahkan, semua ekspresi pointer (kecuali untuk indirection penunjuk, akses anggota penunjuk, dan akses elemen penunjuk), dan fixed pernyataan semuanya tidak lagi dipertimbangkan unsafe, dan ada di C# normal tanpa persyaratan untuk digunakan dalam unsafe konteks. Demikian pula, mendeklarasikan buffer ukuran tetap atau diinisialisasi stackalloc juga sangat legal dalam C#aman. Untuk semua kasus ini, hanya mengakses memori yang tidak aman.
Yang penting, relaksasi pointer ini berlaku terlepas dari apakah perakitan ikut serta dalamaturan keamanan memori yang diperbarui.
Hanya operasi yang benar-benar dereferensi atau secara langsung mengakses pointed-to-memory yang terus memerlukan unsafe konteks.
Ini memungkinkan jalur migrasi bertambah bertahap: pengguna dapat membentuk ulang kode yang ada dengan bergerak unsafe masuk ketika anggota menangani risiko secara internal, atau keluar ketika pemanggil harus berpartisipasi dalam audit, sebelum membalik pengalihan keikutsertaan seluruh perakitan.
Mengingat penulisan ulang yang luas dari unsafe bagian kode dan spesifikasi C# bagian lain yang melekat dalam perubahan ini, itu akan sulit dan kemungkinan tidak berguna untuk memberikan perbedaan baris demi baris dari aturan spesifikasi yang ada. Sebagai gantinya, kami akan memberikan gambaran umum tentang perubahan yang akan dilakukan di bagian tertentu, serta aturan baru tertentu untuk apa yang diizinkan dalam unsafe konteks.
Mendefinisikan ulang ekspresi yang memerlukan konteks yang tidak aman
Ekspresi berikut memerlukan unsafe konteks saat digunakan:
- Tidak langsung penunjuk
- Akses anggota penunjuk
- Akses elemen pointer
- Pemanggilan penunjuk fungsi
- Akses elemen pada buffer ukuran tetap
-
stackallocdalam kondisi yang ditentukan di bawah ini
Selain ekspresi ini, ekspresi dan pernyataan juga dapat secara kondisional memerlukan unsafe konteks jika bergantung pada simbol apa pun yang ditandai sebagai unsafe. Misalnya, memanggil metode yang memerlukan tidak aman akan menyebabkan invocation_expression memerlukan unsafe konteks. Pernyataan dengan pemanggilan yang disematkan (seperti using, foreach, dan serupa) juga dapat memerlukan unsafe konteks ketika mereka menggunakan anggota yang tidak aman .
Ketika kita mengatakan "memerlukan konteks yang tidak aman" atau serupa dalam dokumen ini, itu berarti memancarkan kesalahan bahwa konstruksi memerlukan unsafe konteks untuk digunakan.
Note
Bagian ini mungkin memerlukan perluasan untuk secara resmi menyatakan apa yang harus dipertimbangkan setiap ekspresi dan pernyataan untuk memerlukan unsafe konteks.
Jenis penunjuk
Seperti disebutkan, pointer menjadi tidak lagi secara inheren tidak aman. Setiap referensi ke konteks yang tidak aman dalam §24.3 dihapus. Jenis pointer ada di C# normal dan tidak perlu unsafe membawanya ke keberadaan. Definisi jenis harus dikerjakan menjadi §8.1 dan bagian berikut, sebagai jenis lainnya.
Demikian pula, konversi pointer harus dikerjakan menjadi §10, dengan referensi ke unsafe konteks dihapus.
Demikian pula, ekspresi pointer, kecuali untuk tidak langsung pointer, akses anggota penunjuk, dan akses elemen penunjuk, harus dikerjakan menjadi §12, dengan referensi ke unsafe konteks dihapus. Tidak ada semantik yang berubah tentang arti ekspresi ini; satu-satunya perubahan adalah bahwa mereka tidak lagi memerlukan unsafe konteks untuk digunakan.
Untuk tidak langsung penunjuk, akses anggota penunjuk, dan akses elemen penunjuk, operator ini tetap tidak aman, karena memori akses ini yang tidak dikelola runtime. Mereka tetap dalam §24, dan terus memerlukan konteks untuk unsafe digunakan. Setiap penggunaan di luar unsafe konteks adalah kesalahan.
Tidak ada semantik tentang operator ini yang berubah; mereka masih terus berarti persis sama hal yang mereka lakukan hari ini. Ekspresi ini harus selalu terjadi dalam unsafe konteks.
Pernyataan tetap berpindah ke §13, dengan referensi ke unsafe konteks dihapus.
Penunjuk fungsi belum dimasukkan ke dalam spesifikasi C# utama, tetapi sama-sama terpengaruh; semuanya kecuali pemanggilan pointer fungsi dipindahkan ke spesifikasi standar.
Ekspresi pemanggilan penunjuk fungsi harus selalu terjadi dalam unsafe konteks.
Buffer ukuran tetap
Cerita untuk buffer berukuran tetap mirip dengan pointer. Definisi buffer ukuran tetap tidak berbahaya sendiri, dan pindah ke §16.3. Mengakses buffer ukuran tetap dalam ekspresi sama-sama aman, kecuali ekspresi terjadi sebagai primary_expressionelement_access; ini dievaluasi sebagai pointer_element_access, yang tidak aman, sesuai aturan di atas.
Alokasi memori tumpukan
Sekali lagi, cerita untuk alokasi tumpukan sangat mirip dengan pointer. Mengonversi stackalloc ke penunjuk tidak lagi tidak aman; itu adalah penangguhan penunjuk yang tidak aman. Namun, kami menambahkan satu aturan baru:
Stackalloc_expression tidak aman jika semua pernyataan berikut ini benar:
-
stackalloc_expression sedang dikonversi ke
Span<T>atauReadOnlySpan<T>. - stackalloc_expression tidak memiliki stackalloc_initializer.
-
stackalloc_expression digunakan dalam anggota yang telah
SkipLocalsInitAttributediterapkan.
Dalam konteks ini, ruang tumpukan yang dihasilkan dapat memiliki konten memori yang tidak diketahui, dan sedang dikonversi ke jenis yang menyediakan pembungkus aman di sekitar akses memori yang tidak dikelola. Ini melanggar kontrak Span<T> dan ReadOnlySpan<T>, sehingga harus tunduk pada pengawasan ekstra oleh penulis dan peninjau kode tersebut.
Tidak seperti perubahan lain pada unsafe aturan yang merupakan relaksasi, ini adalah pengetatan, dan karenanya hanya berlaku di bawah keikutsertaan pada aturan keamanan memori yang diperbarui untuk menghindari jeda.
Note
Ini berarti bahwa menetapkan stackalloc ke penunjuk selalu aman, terlepas dari konteksnya.
Perhatikan bahwa stackalloc jenis terkelola tetap menjadi kesalahan.
Kita mungkin perlu secara eksplisit menyebutkan dalam spesifikasi bahasa bahwa stackalloc penunjuk tidak bertahan .await
Sebelum fitur ini, pointer tidak diizinkan dalam async metode, sehingga ini tidak dapat diamati sebelumnya.
await dalam unsafe konteks
await ekspresi diperbolehkan dalam unsafe konteks. Ini memungkinkan metode asinkron untuk langsung menunggu operasi yang tidak aman tanpa terlebih dahulu menumpahkan yang dapat ditunggu ke variabel sementara:
async Task M()
{
unsafe
{
await DoUnsafeAsync();
}
}
unsafe Task DoUnsafeAsync() => ...;
Konteks masih unsafe mewakili batas audit keselamatan manual programmer. Jika penunjuk, penunjuk fungsi, atau nilai tidak aman lainnya disimpan di seluruh await, bahasa tidak mencoba membuktikan bahwa penggunaan aman setelah dimulai kembali; tanggung jawab tersebut tetap bersama unsafe penulis kode. Ini konsisten dengan sisa kode yang tidak aman, yang mengizinkan operasi seperti menyimpan pointer di bidang atau menggunakan pointer setelah masa pakai referensi mereka berakhir.
Namun, await tetap tidak diizinkan di dalam pernyataan fixed .
fixed Pernyataan menyematkan variabel yang dapat dipindahkan selama durasi pernyataan, dan memungkinkan penangguhan saat pernyataan aktif akan memerlukan masa pakai penyematan untuk menjangkau titik suspensi asinkron.
Pembaruan csharpstandard yang sesuai ada dalam fungsi > Asinkron §15.15.1 Umum. Di seluruh bagian ini, coretan menunjukkan teks dihapus dari spesifikasi yang ada, dan tebal menunjukkan teks ditambahkan.
Ini adalah kesalahan waktu kompilasi untuk daftar parameter formal fungsi asinkron untuk menentukan parameter , ,
inatauoutapa punref, atau parameter jenisref structapa pun.Ini adalah kesalahan waktu kompilasi untuk konteks yang tidak aman (§24,2) untuk berisi
awaitekspresi (§12.9.8) atauyield returnpernyataan (§13.15).Ini adalah kesalahan waktu kompilasi untuk
fixedpernyataan (§24,7) untuk berisiawaitekspresi (§12.9.8).
sizeof
Untuk jenis tertentu yang telah ditentukan sebelumnya, sizeof selalu konstan dan aman (§12.8.19) dan tetap tidak berubah.
Untuk jenis lain, sizeof digunakan untuk memerlukan konteks yang tidak aman (§24.6.9) tetapi sekarang aman terlepas dari keikutsertaan aturan keamanan memori yang diperbarui.
Mengambil alih, mewarisi, dan implementasi
Ini adalah kesalahan keamanan memori untuk ditambahkan unsafe di tingkat anggota dalam setiap penimpaan atau implementasi anggota yang awalnya tidak perlu aman , karena penelepon mungkin menggunakan definisi dasar dan tidak melihat penambahan apa pun oleh implementasi turunan unsafe .
Delegasi dan lambda
Ini adalah kesalahan keamanan memori untuk mengonversi anggota yang memerlukan tidak aman ke jenis delegasi di luar unsafe konteks.
Mendelegasikan jenis dan jenis fungsi tidak boleh memerlukan-tidak aman.
Ini adalah kesalahan waktu kompilasi untuk diterapkan unsafe pada simbol lambda.
extern
Karena extern metode adalah untuk lokasi asli yang tidak dapat dijamin oleh runtime, pengkompilasi tidak dapat mengetahui apakah lokasi tersebut aman atau tidak aman.
Bahkan metode yang hanya mengambil unmanaged parameter menurut nilai tidak dapat dipanggil dengan aman oleh C#, karena konvensi panggilan yang digunakan untuk metode ini dapat salah ditentukan oleh pengguna dan harus diverifikasi secara manual dengan ditinjau.
Oleh karena itu, di bawah aturan keamanan memori yang diperbarui, pengkompilasi mengharuskan setiap extern metode ditandai secara eksplisit sebagai unsafe atau safe.
extern metode dari rakitan yang menggunakan aturan keamanan memori warisan tidak dianggap secara unsafe implisit karena extern dianggap sebagai detail implementasi yang bukan bagian dari permukaan publik.
extern tidak dijamin akan dipertahankan dalam rakitan referensi.
Perhatikan bahwa ini berbeda dari mode kompat yang berlaku untuk rakitan aturan warisan juga karena metode dengan penunjuk dalam tanda tangan akan selalu memerlukan konteks yang tidak aman di situs panggilan.
Pengubah dan konteks yang tidak aman
Saat ini, seperti yang dibahas oleh spesifikasi konteks yang tidak aman, unsafe berperilaku dengan cara leksikal, menandai seluruh isi tekstual yang terkandung oleh unsafe blok sebagai unsafe konteks (kecuali untuk badan iterator), dan juga beberapa konteks di sekitarnya jika terjadi deklarasi:
class A : Attribute
{
public A(object o) { }
}
class C
{
[A(default(int*[]))] void M1() { } // error: using pointers outside `unsafe` context
[A(default(int*[]))] unsafe void M2() { } // ok
}
Dengan ikut serta dalam aturan keamanan memori yang diperbarui, unsafe pada anggota menandainya sebagai tidak aman, memperluas kewajiban audit kepada pemanggil, dan tidak memperkenalkan unsafe konteks (sebagai gantinya, hanya wilayah eksplisit unsafe dalam isi yang menetapkan unsafe konteks).
unsafe pada deklarasi berikut menghasilkan kesalahan karena tidak memiliki arti lagi:
-
delegate, - konstruktor statis,
- destruktor,
- deklarasi jenis (
class,struct, dll.).
unsafe pada konstruktor memperkenalkan unsafe konteks di dalam inisialisasinya, yaitu, unsafe konstruktor dapat memanggil konstruktor yang memerlukan tidak amanbase atau this konstruktor.
Dalam posisi deklarasi, jenis dengan konstruktor yang tidak aman tanpa parameter tidak memenuhi new() batasan atau struct .
Dalam posisi ekspresi, jenis dengan konstruktor tanpa parameter memerlukan-tidak aman memenuhi new() atau struct batasan hanya di dalam unsafe konteks.
class Unsafe { public unsafe Unsafe() { } }
class C<T> where T : new();
class D : C<Unsafe> // error: Unsafe cannot satisfy new() in this declaration position
{
void M()
{
_ = new C<Unsafe>(); // error: Unsafe cannot satisfy new() in this expression position
unsafe { _ = new C<Unsafe>(); } // ok: unsafe context
}
}
unsafe
/
safe pada anggota tidak diterapkan ke fungsi anonim atau lokal berlapis di dalam anggota.
Hal yang sama berlaku untuk fungsi anonim dan lokal yang dinyatakan di dalam unsafe blok (fungsi tersebut masih dalam unsafe konteks seperti biasa, tetapi tidak menjadi tidak aman).
Untuk menandai fungsi lokal sebagai memerlukan-tidak aman, fungsi tersebut harus ditandai secara manual sebagai unsafe.
Lambda tidak dapat ditandai memerlukan-tidak aman ( unsafe kata kunci tidak diizinkan pada mereka).
Ketika anggota adalah partial, kedua bagian harus menyetujui unsafe/safe pengubah, tidak berubah dari aturan C# hari ini.
partial class C1
{
public partial void M1(); // Error: both parts must be unsafe, or neither can be
public partial unsafe void M2();
}
partial class C1
{
public unsafe partial void M1() => Console.WriteLine("hello world");
public partial void M2() => Console.WriteLine("hello world"); // Error: both parts must be unsafe, or neither can be
}
Untuk properti, get dan set/init aksesor dapat dideklarasikan secara independen sebagai unsafe/safe.
Jika aksesor tidak memiliki pengubah unsafe/safe , mereka mewarisinya dari properti .
Mirip readonly dengan pengubah, kami memperkenalkan batasan berikut untuk properti dan aksesor:
-
unsafe/safepengubah dapat diterapkan baik ke properti atau aksesornya, bukan keduanya. - Ini adalah kesalahan untuk menerapkan pengubah yang sama
unsafe/safeke semua aksesor properti (pengubah tersebut harus diterapkan ke properti sebagai gantinya).
Saat ini tidak dimungkinkan untuk menempatkan pengubah apa pun pada pengakses peristiwa, dan proposal ini tidak mengubahnya, yaitu, add dan remove pengakses peristiwa tidak dapat dideklarasikan secara independen sebagai unsafe.
Hanya jika seluruh peristiwa ditandai sebagai unsafe, itu berarti bahwa aksesor tidak aman; jika tidak, mereka aman.
Fields
unsafe pada bidang juga menandainya sebagai memerlukan tidak aman, dan tidak memperkenalkan unsafe konteks dalam penginisialisasinya.
Mode kompatiasi juga berlaku untuk bidang.
Menandai properti atau peristiwa sebagai unsafe tidak membuat bidang penolongnya memerlukan-tidak aman.
Dalam jenis dengan [StructLayout(LayoutKind.Explicit)] atau [ExtendedLayout], semua bidang instans harus ditandai baik safe atau unsafe.
Jika bidang "disembunyikan" di belakang properti otomatis atau peristiwa seperti bidang, safe/unsafe persyaratan dipindahkan ke properti otomatis atau peristiwa seperti bidang sebagai gantinya.
Metadata
Ketika rakitan dikompilasi dengan aturan keamanan memori baru, assembly ditandai dengan MemorySafetyRulesAttribute (dirinci di bawah), diisi dengan 15 sebagai versi bahasa. Ini adalah sinyal kepada setiap konsumen hilir bahwa setiap anggota yang ditentukan dalam assembly akan diatribusikan dengan benar dengan RequiresUnsafeAttribute (dirinci di bawah) jika unsafe konteks diperlukan untuk memanggil mereka.
Setiap anggota dalam rakitan seperti itu yang tidak ditandai dengan RequiresUnsafeAttribute tidak memerlukan unsafe konteks untuk dipanggil, terlepas dari jenis dalam tanda tangan anggota.
Ini adalah kesalahan untuk menerapkan MemorySafetyRulesAttribute atau RequiresUnsafeAttribute ke simbol apa pun secara eksplisit di sumber.
Kompilator mengabaikan RequiresUnsafeAttributeanggota yang ditandai dari rakitan yang menggunakan aturan keamanan memori warisan (sebagai gantinya, mode kompat digunakan di sana).
Ketika anggota non-jenis memerlukan tidak aman, pengkompilasi akan mensintesis aplikasi RequiresUnsafeAttribute pada anggota dalam metadata.
Definisi MemorySafetyRulesAttribute dan RequiresUnsafeAttribute disintesis oleh pengkompilasi jika perlu per aturan anggota terkenal standar.
namespace System.Runtime.CompilerServices
{
/// <summary>Indicates the language version of the memory safety rules used when the module was compiled.</summary>
[AttributeUsage(AttributeTargets.Module, Inherited = false)]
public sealed class MemorySafetyRulesAttribute : Attribute
{
/// <summary>Initializes a new instance of the <see cref="MemorySafetyRulesAttribute"/> class.</summary>
/// <param name="version">The language version of the memory safety rules used when the module was compiled.</param>
public MemorySafetyRulesAttribute(int version) => Version = version;
/// <summary>Gets the language version of the memory safety rules used when the module was compiled.</summary>
public int Version { get; }
}
[AttributeUsage(AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
public sealed class RequiresUnsafeAttribute : Attribute
{
}
}
Mode ringkas
Untuk tujuan kompatasi, dan untuk mengurangi jumlah negatif palsu yang terjadi saat mengaktifkan aturan baru, kami memiliki aturan fallback untuk modul yang belum diperbarui ke aturan baru. Untuk modul tersebut, anggota dianggap tidak aman jika berisi jenis pointer atau penunjuk fungsi di suatu tempat di antara jenis parameter atau jenis pengembaliannya (dapat disarangkan dalam jenis non-pointer, misalnya, int*[]).
Perhatikan bahwa ini tidak berlaku untuk penunjuk dalam jenis batasan (misalnya, where T : I<int*[]>) karena mereka tidak akan memerlukan konteks yang tidak aman di situs panggilan sebelumnya.
Ini tidak termasuk parameter generik yang digantikan (misalnya, metode I<T>.M(T) ketika diganti T ) int*[]karena tidak ada cara yang aman untuk anggota target untuk menggunakan jenis pointer tersebut untuk apa pun.
Mode kompat seperti itu memerlukan anggota yang unsafe memerlukan konteks untuk digunakan bahkan dari penelepon yang belum memilih aturan keamanan memori yang diperbarui.
Itu harus menghindari "penurunan" di mana hanya memperbarui LangVersion (tetapi tidak memperbarui versi aturan keselamatan memori) membuat sebagian besar operasi pointer aman (termasuk fungsi panggilan dengan penunjuk dalam tanda tangan yang kemungkinan akan ditandai sebagai tidak aman saat memilih aturan yang diperbarui), dan karenanya membuat kode kurang terlindungi di jendela migrasi ini.
VB
Kami tidak perlu menambahkan dukungan ke Visual Basic untuk anggota yang tidak aman karena tidak unsafe ada konteks dalam VB hari ini dan tidak ada cara untuk bekerja dengan penunjuk di sana juga.
unsafe Ekspresi
Ekspresi unsafe memperkenalkan konteks minimal unsafe untuk mengevaluasi ekspresi tunggal. Ini berguna dalam situasi di mana unsafe blok akan secara tidak perlu melebarkan unsafe cakupan, atau di mana unsafe blok tidak dapat digunakan secara sintis (seperti dalam catch filter, penginisialisasi bidang, dan inisialisasi konstruktor).
Syntax
Ditambahkan unsafe_expression sebagai primary_no_array_creation_expression:
unsafe_expression
: 'unsafe' '(' expression ')'
;
Ini tunduk pada persyaratan yang sama AllowUnsafeBlocks dengan kata kunci di unsafe tempat lain.
Semantik
Menetapkan unsafe_expressionunsafe konteks untuk mengevaluasi ekspresinya. Ini berarti bahwa dereferensi penunjuk, pemanggilan penunjuk fungsi, dan panggilan ke anggota yang memerlukan tidak aman semuanya diizinkan dalam ekspresi tertutup. Jenis dan nilai adalah unsafe_expression jenis dan nilai ekspresi yang diapit.
Konteks unsafe yang unsafe_expression ditetapkan oleh tidak melampaui tanda kurung penutupnya.
Contoh motivasi dan migrasi
Beberapa posisi syntactic tidak mengakui unsafe blok sama sekali, namun mungkin berisi subekspresi yang memanggil anggota yang memerlukan anggota yang tidak aman . Tanpa unsafe ekspresi, memigrasikan kode tersebut memerlukan ekstraksi sub-ekspresi yang tidak aman ke dalam fungsi lokal pembantu atau variabel sementara, atau menggunakan blok yang lebih unsafe luas dari yang diperlukan, yang mengaburkan niat dan meningkatkan verbositas.
await pada metode yang memerlukan tidak aman . Ketika metode yang ditunggu menjadi tidak aman, unsafe blok dapat digunakan di sekitar keseluruhan await, tetapi unsafe ekspresi dapat menjaga konteks yang tidak aman terbatas pada operasi yang memerlukannya:
// Without unsafe expressions: the unsafe context includes the await expression
unsafe
{
// SAFETY: Discharges obligations because reasons
await DoWork();
}
// With unsafe expressions: the unsafe context wraps only the call
// SAFETY: Discharges obligations because reasons
await unsafe(DoWork());
Menangkap filter. Filter whencatch klausa adalah ekspresi, bukan isi pernyataan. Blok unsafe hanya dapat mengelilingi pernyataan, sehingga tidak ada tempat untuk meletakkan satu di sekitar hanya ekspresi filter. Ketika metode yang digunakan dalam filter menjadi tidak aman, satu-satunya alternatif tanpa unsafe ekspresi adalah pembantu:
// Without unsafe expressions: must spill to a local function
static bool FilterHelper(Exception e)
{
// SAFETY: Discharges obligations because reasons
unsafe { return NowUnsafeCall(e); }
}
try
{
await DoWork(); // 'await' here prevents wrapping the whole try/catch in 'unsafe'
}
catch (Exception e) when (NowUnsafeCall(e))
{
}
// With unsafe expressions: inline and minimal scope
try
{
await DoWork();
}
// SAFETY: Discharges obligations because reasons
catch (Exception e) when (unsafe(NowUnsafeCall(e)))
{
}
Penginisialisasi bidang. Di bawah aturan yang diperbarui, unsafe pada bidang tidak memperkenalkan unsafe konteks dalam penginisialisasinya. Saat penginisialisasi bidang memanggil anggota yang memerlukan tidak aman , unsafe ekspresi menyediakan konteks tanpa memerlukan metode pembantu:
// Without unsafe expressions: must spill to a helper method
static int InitialValue()
{
// SAFETY: Discharges obligations because reasons
unsafe { return ReadFromPointer(); }
}
static int _value = InitialValue();
// With unsafe expressions: inline
// SAFETY: Discharges obligations because reasons
static int _value = unsafe(ReadFromPointer());
Penginisialisasi konstruktor.
this(...) dan base(...) daftar argumen penginisialisasi adalah ekspresi, bukan badan pernyataan. Jika salah satu argumen tersebut memanggil anggota yang memerlukan tidak aman , tidak ada lagi tempat untuk menyisipkan unsafe blokir, sehingga panggilan harus dipindahkan ke pembantu:
class C(int x)
{
// SAFETY: Discharges obligations because reasons
C() : this(unsafe(GetUnsafeValue()))
{
}
}
class Derived : Base
{
// SAFETY: Discharges obligations because reasons
Derived() : base(unsafe(GetUnsafeValue()))
{
}
}
Panggilan sebaris. Lebih umumnya, membungkus hanya sub-ekspresi yang memerlukan-tidak aman menjaga cakupan audit tetap ketat dan menghindari penarikan kode aman di sekitarnya (seperti evaluasi argumen atau panggilan metode yang berisi) ke dalam unsafe konteks:
extern int Add(int i1, int i2); // Some fancy extern addition function
// Code I want to write:
// SAFETY: Discharges obligations because reasons
Console.WriteLine(unsafe(Add(1, 2)));
// Code I have to write without unsafe expressions, option 1
// (unsafe context unnecessarily includes the WriteLine call):
// SAFETY: Discharges obligations because reasons
unsafe
{
Console.WriteLine(Add(1, 2));
}
// Code I have to write without unsafe expressions, option 2
// (very verbose and harder to read):
int result;
// SAFETY: Discharges obligations because reasons
unsafe
{
result = Add(1, 2);
}
Console.WriteLine(result);
Pertanyaan
(dijawab) Gunakan RequiresUnsafeAttribute untuk menunjukkan anggota yang memerlukan tidak aman
Alih-alih menggunakan unsafe kata kunci pada anggota untuk menunjukkan anggota yang memerlukan anggota yang tidak aman , kita dapat menggunakan atribut (RequiresUnsafeAttribute) yang diterapkan ke anggota (dan tidak mengubah arti unsafe pengubah pada anggota).
Keuntungan dari unsafe:
- mirip dengan bahasa lain dan karenanya lebih mudah dipahami,
- lebih dapat ditemukan daripada atribut.
Keuntungan atribut (atau kata kunci lain):
- menghindari melanggar anggota yang ada yang ditandai sebagai
unsafe, - adopsi bertambah bertahap (anggota per anggota),
- tidak memaksa menandai seluruh isi sebagai
unsafe(bahkan denganunsafekata kunci yang dapat kita ubahunsafeuntuk tidak memiliki efek pada tubuh), - memungkinkan menekan semua kesalahan yang memerlukan kesalahan yang tidak aman tanpa perlu menandai anggota itu sendiri sebagai requires-unsafe (contoh).
Diskusi:
- LDM 2025-11-12: gunakan kata kunci
- LDM 2026-01-26: gunakan atribut
- LDM 2026-04-06: gunakan kata kunci
Jawaban: gunakan kata kunci unsafe untuk menunjukkan anggota yang memerlukan tidak aman .
Fungsi lokal/konteks aman lambda
Saat ini unsafe pada tubuh metode dilingkup secara leksikal. Setiap fungsi lokal berlapis atau lambda mewarisi ini, dan tubuhnya berada dalam konteks memori yang tidak aman.
Apakah perilaku ini yang ingin kita simpan dalam bahasa?
Perhatikan bahwa jika kita menyimpan unsafe sebagai pengubah yang digunakan untuk mengekspos bahwa pemanggil harus tidak aman, ini kemudian dapat berdampak pada tanda tangan metode.
Seperti yang saat ini diusulkan, fungsi anonim berlapis dan lokal tidak menyimpan konteks tidak aman dari anggota yang berisinya.
Mendelegasikan jenis unsafety
Kita dapat mengizinkan penandaan jenis delegasi dan lambda (dan jenis fungsi) karena memerlukan-tidak aman.
Ini akan memerlukan beberapa aturan tambahan (konteks luar unsafe ):
- melarang penggunaan delegasi yang memerlukan tidak aman sebagai argumen jenis,
- melarang konversi delegasi tersebut ke apa pun yang tidak memerlukan tidak aman (
Delegate,Expression, danobject), Tanpa ini, ada risiko memaksaunsafeanotasi di tempat yang salah dan memiliki area di mana areaunsafenyata ty tidak dipanggil dengan benar.
Konversi grup Lambda/metode ke jenis delegasi yang aman
Jika kami mengizinkan unsafe lambda dan delegasi, haruskah konversi lambda atau grup metode yang memerlukan tidak aman ke jenis delegasi yang tidak diperlukan-tidak aman diizinkan tanpa peringatan atau kesalahan dalam unsafe konteks? Jika kita tidak melakukan ini, maka itu bisa cukup menyakitkan untuk berbagai bagian ekosistem, terutama enumerable yang diteruskan melalui kueri LINQ.
Jenis alami grup Lambda/metode
Saat ini, satu-satunya dampak nyata pada semantik dan codegen (selain metadata tambahan) adalah mengubah function_type grup lambda atau metode ketika unsafe berada di tanda tangan. Jika kita menghindari hal ini, maka tidak akan ada dampak nyata juga, yang dapat memberi pengadopsi lebih percaya diri bahwa perilaku tidak berubah secara halus di bawah tenda.
Note
Jika kita memutuskan untuk menjaga kemampuan untuk memiliki unsafe lambda, kita perlu memperbarui proposal ini untuk menyertakan perubahan sintaks untuk memungkinkan lambda dideklarasikan unsafe di tempat pertama.
Penunjuk ke jenis terkelola
C# 11 memungkinkan pointer untuk mengelola jenis dengan peringatan.
Haruskah kita melonggarkan peringatan itu untuk alamat operasi?
Kami pikir masalahnya hanya ketika pengguna mendereferensikan pointer tersebut, yang berada di bawah aturan evolusi normal yang tidak aman.
Tapi bagaimana dengan sizeof?
stackalloc seperti yang diinisialisasi
Saat ini, spesifikasi selalu menganggap stackalloc memori sebagai tidak diinisialisasi, dan mengatakan bahwa konten tidak terdefinisi kecuali dibersihkan atau ditetapkan secara manual. Apakah kita menganggap ini bug spesifikasi, atau apakah kita perlu mengubah apa yang kita pertimbangkan unsafe untuk stackalloc tujuan?
stackalloc Aturan
LDM harus mengonfirmasi aturan yang stackalloc ditentukan di atas dan apakah aturan tersebut harus berlaku terlepas dari keikutsertaan seperti perubahan terkait pointer lainnya.
AllowUnsafeBlocks
Arti AllowUnsafeBlocks saat ini tidak berubah - harus diatur ke true agar dapat menggunakan unsafe kata kunci atau SkipLocalsInitAttribute.
Haruskah kita tidak mewajibkannya untuk SkipLocalsInitAttribute di bawah aturan yang diperbarui karena BCL dapat menandai atribut tersebut sebagai memerlukan-tidak aman?
Haruskah kita memerlukannya untuk safe kata kunci juga?
Haruskah kita memerlukannya untuk unsafe blok dan unsafe deklarasi anggota atau kombinasi lainnya?
(dijawab) unsafe Ekspresi
Bahasa lain dengan fitur yang lebih komprehensif unsafe telah ditambahkan unsafe sebagai ekspresi yang meningkatkan ergonomi pengguna dan memungkinkan penulis membatasi dengan lebih tepat di mana unsafe digunakan. Apakah ini sesuatu yang ingin kita miliki di C#? Pertimbangkan panggilan sebaris ke unsafe anggota yang menangani keselamatan secara langsung: saat ini, penulis harus membungkus seluruh pernyataan dalam unsafe blok, memperluas cakupan unsafe konteks, atau mereka perlu memecah panggilan fungsi dalam menjadi variabel perantara.
extern int Add(int i1, int i2); // Some fancy extern addition function
// Code I want to write:
Console.WriteLine(unsafe(Add(1, 2)));
// Code I have to write option 1, unsafe context unnecessary includes the WriteLine call
unsafe
{
Console.WriteLine(Add(1, 2));
}
// Code I have to write option 2, very verbose and harder to read:
int result;
unsafe
{
result = Add(1, 2);
}
Console.WriteLine(result);
Jawaban: ya. Lihat bagian desain terperinci.
(dijawab) await dalam unsafe konteks
Haruskah kita melonggarkan pembatasan sekeliling await ekspresi dalam unsafe konteks?
Terutama mengizinkan await UnsafeMethod() berguna karena jika tidak, pengguna harus menulis ulang itu ke Task t; unsafe { t = UnsafeMethod(); } await t;.
Lihat ref/unsafe di iterator/asinkron untuk detail selengkapnya.
-
LDM 2026-05-27:
awaitmembutuhkanunsafeproposal konkret -
LDM 2026-07-01: izinkan
awaitmasukunsafe
Jawaban: ya. Lihat await dalam unsafe konteks.
Konteks unsafe dan relaksasi lainnya
Menindaklanjuti await dalam unsafe konteks, haruskah kita juga mengizinkan yield dalam unsafe konteks, dan parameter penunjuk untuk metode asinkron dan iterator?
Lihat ref/unsafe di iterator/asinkron untuk detail selengkapnya.
Haruskah kita juga mengizinkan &UnsafeMethod dalam konteks yang aman? Hari ini sebagai proposal berdiri, ini memerlukan unsafe konteks jika metode ditandai sebagai unsafe.
Tetapi karena kita hanya mendapatkan alamatnya, yang akan membutuhkan unsafe konteks ketika didereferensikan/dipanggil, kita dapat mengizinkan alamat itu sendiri dalam konteks yang aman.
Relaksasi yang tidak aman terjaga di LangVersion
Haruskah kita membuat relaksasi konteks yang tidak aman tanpa syarat di LangVersion?
- LDM 2026-04-06: tidak bersyarah pada versi aturan keamanan memori
- Bagaimana dengan LangVersion?
(dijawab) unsafe pada jenis
Kita dapat mempertimbangkan untuk tidak secara otomatis membuat seluruh cakupan leksikal jenis unsafe menjadi unsafe konteks dan memperingatkan untuk unsafe jenis karena tidak akan memiliki arti.
-
LDM 2025-11-12:
unsafepada jenis tidak akan memiliki arti - LDM 2026-05-13: itu akan menjadi kesalahan (dapat dilihat kembali berdasarkan umpan balik)
Jawaban: unsafe pada jenis adalah kesalahan (dapat ditinjau kembali berdasarkan umpan balik) di bawah aturan yang diperbarui.
(dijawab) Perbolehkan penekanan memerlukan kesalahan yang tidak aman dalam skenario kasus tepi
Bagaimana kita harus mengizinkan penindasan memerlukan kesalahan yang tidak aman dalam skenario berikut?
class A : System.Attribute
{
unsafe public A() { } // declaring requires-unsafe constructor
}
class C
{
[A] public void M() { } // error: applying requires-unsafe `A..ctor`
}
class B : A
{
public B() { } // error: calling requires-unsafe `A..ctor` (implicit `: base()`)
}
class X<T> where T : new();
class D
{
public void M(X<A> x) { } // error: using `X` which uses requires-unsafe `A..ctor`
}
Untuk menekan kesalahan yang diperlukan tidak aman , kita perlu memperkenalkan unsafe konteks dalam tanda tangan anggota tersebut.
unsafe Tetapi kata kunci tidak memperkenalkan unsafe konteks lagi.
Kita dapat membuat unsafe memperkenalkan unsafe konteks dalam tanda tangan tetapi memaksa membuat konstruktor membutuhkan tidak aman ketika ingin memanggil konstruktor yang membutuhkan tidak aman tampaknya tidak beruntung.
Kita dapat membuat penggunaan yang tidak aman dalam peringatan tanda tangan yang dapat ditekan pengguna di tempat jika mereka mencapai kasus tepi langka ini.
Ada masalah serupa dengan jenis karena unsafe pada mereka tidak memperkenalkan unsafe konteks baik:
class A : Attribute
{
unsafe public A() { } // declaring requires-unsafe constructor
}
[A] class C; // error: applying requires-unsafe `A..ctor`
class B() : A(); // error: calling requires-unsafe `A..ctor`
class X<T> where T : new();
class D : X<A>; // error: inheriting from `X` which uses requires-unsafe `A..ctor`
-
LDM 2026-05-13:
- kode yang tidak dapat dieksekusi, seperti aplikasi atribut, harus tetap menjadi kesalahan yang tidak dapat ditekan (sampai kami mendengar beberapa umpan balik)
- kasus tepi lainnya seperti
new()juga dapat tetap menjadi kesalahan sampai kami mendengar umpan balik - jangan melarang mendeklarasikan konstruktor yang tidak memerlukan parameter yang tidak aman
-
unsafehanya penginisialisasi konstruktor yang dapat memanggil konstruktor yang memerlukan tidak amanbaseatauthiskonstruktor
koleksi params
class C
{
unsafe public C() { } // declaring requires-unsafe constructor
}
class B
{
public void M(params C c) { }
}
Deklarasi ini bisa saja diizinkan karena memanggil metode tersebut tetap memerlukan unsafe konteks karena kumpulan yang tidak aman params dibangun di situs panggilan.
Meskipun itu membuat deklarasi secara efektif membutuhkan tidak aman, jadi kita hanya bisa memerlukan unsafe anotasi atau setidaknya memperingatkan tentang fakta itu.
Perhatikan bahwa kasus pengumpulan params adalah kesalahan hari ini sejalan dengan bagaimana fitur serupa lainnya berperilaku di sini (Obsolete, UnmanagedCallersOnly), tetapi itu mungkin merupakan bug implementasi.
Anggota terkenal
Untuk kesederhanaan dan kewarasan implementasi, kami mengusulkan bahwa kompilator bebas untuk mengasumsikan bahwa semua anggota terkenal (seperti Array.Length) dapat diasumsikan aman (yaitu, tidak memerlukan-tidak aman).
Dokumen Xml
Meneruskan kewajiban kepada penelepon datang dengan tanggung jawab untuk memperjelas apa kewajiban itu. Haruskah kita meresmikan ini di luar apa yang sudah dapat diwakili dalam dokumen xml?
Anggota yang ditandai unsafe harus memiliki komentar yang menunjukkan apa yang diperlukan bagi pemanggil untuk melakukan untuk memastikan kode sudah benar.
Untuk mempermudah melihat dan lebih mudah untuk membedakan dalam dokumentasi, tag dokumen XML baru akan sangat membantu: <safety />.
Diharapkan bahwa semua pra/pasca-kondisi akan ditempatkan di <safety> blok.
Untuk mendokumentasikan penalaran setiap unsafe blok, kami merekomendasikan penggunaan // SAFETY komentar, mirip dengan Rust.
Haruskah kita juga memeriksanya oleh pengkompilasi (misalnya, memiliki beberapa peringatan off-by-default), atau meninggalkannya ke penganalisis?
- LDM 2026-05-27: tidak ada keputusan konkret
Peringatan yang lebih tidak unsafe berarti
Haruskah lebih banyak deklarasi menghasilkan peringatan atau kesalahan yang tidak unsafe berarti?
Misalnya, metode dengan badan kosong (atau extern), dll. Kami sudah memiliki penganalisis IDE untuk yang tidak perlu unsafe sekalipun.
Harus [ModuleInitializer] unsafe void M() { } kesalahan, mirip dengan konstruktor statis?
(dijawab) unsafe bidang
Hari ini, tidak ada proposal yang dibuat unsafe di lapangan. Namun, kita mungkin perlu menambahkannya, sehingga setiap bacaan dari atau tulis ke bidang yang ditandai sebagai unsafe harus dalam unsafe konteks. Ini akan memungkinkan kita untuk lebih baik membuat anotasi kekhawatiran sekeliling kode seperti:
class SafeWrapper
{
internal byte* _p;
public void DoStuff()
{
unsafe
{
// ... validate that the object state is good ...
// ... perform operation with _p ....
}
}
}
// Elsewhere in safe code:
void M(SafeWrapper w)
{
w._p = stackalloc byte[10];
}
Haruskah kita juga menandai bidang dukungan properti otomatis sebagai unsafe?
Agar konsisten dengan keputusan kami untuk anggota, ada baiknya untuk membuat unsafe di lapangan juga tidak memperkenalkan unsafe konteks.
Jika operasi yang memerlukan tidak aman digunakan di inisialisasi bidang, pengguna selalu dapat merangkumnya ke dalam metode, atau kami dapat memperkenalkan unsafe ekspresi.
-
LDM 2026-05-13: bidang dapat ditandai memerlukan-tidak aman melalui
unsafe; inisialisasi tidak dalamunsafekonteks.
(dijawab) Tata letak eksplisit
Haruskah bidang dalam struktur ditandai sebagai [StructLayout(Explicit)] atau [ExtendedLayout] diperlukan untuk ditandai sebagai unsafe?
Rekomendasi: ya.
-
LDM 2026-05-13: ya, memerlukan atau
unsafesafe, sama seperti untukexterns.
Bidang tata letak dan backing eksplisit
Jika pengompilasi mensintesis bidang dukungan untuk properti otomatis atau peristiwa seperti bidang dalam Explicit/Extended jenis, haruskah kita perlukan safe/unsafe pada properti/peristiwa sebagai gantinya?
Jika tidak, pengguna akan dipaksa untuk memperluas deklarasi otomatis ini ke bidang manual ditambah deklarasi anggota pembungkus.
Bagaimana dengan parameter konstruktor utama yang mendapatkan bidang dukungan?
Pengubah safe dan unsafe saat ini tidak diizinkan pada deklarasi parameter.
Anggota yang disintesis
Ketika anggota yang dideklarasikan pengguna ditandai sebagai tidak aman, haruskah anggota yang disintesis kompilator terkait yang tidak dapat diuji tetapi dapat dipanggil melalui pantulan (seperti MoveNext untuk iterator) juga ditandai dengan [RequiresUnsafe] atribut dalam metadata?
Perhatikan bahwa ini tidak berlaku untuk aksesor properti dan peristiwa di mana kita sudah menentukan dengan tepat kapan mereka memerlukan tidak aman dan karenanya mereka mendapatkan [RequiresUnsafe] atribut dalam metadata dengan sesuai.
[Out] dan [SkipLocalsInit]
Karena misalnya VB tidak menjamin bahwa [Out] parameter diinisialisasi, dalam kombinasi dengan [SkipLocalsInit], memanggil parameter tersebut dapat dipertimbangkan unsafe dalam C#.
Di sisi lain, rasanya seperti masalah penerima panggilan bahwa itu tidak menegakkan kontraknya [Out] (demikian pula bisa tidak aman dengan banyak cara lain).
Jika kita memutuskan kasus-kasus tersebut harus unsafe, kita dapat mengecualikan metode yang kita tahu telah memilih (saat ini berasal dari C# yang menjamin penggunaan [Out] yang benar tetapi jika bahasa lain menerapkan aturan baru, mereka juga harus menjaminnya).
Mengambil alamat variabel yang tidak diinisialisasi
Hari ini, mengambil alamat variabel yang tidak pasti ditetapkan dapat mempertimbangkan variabel yang pasti ditetapkan, mengekspos anggota yang tidak diinisialisasi. Kami memiliki beberapa opsi untuk memecahkannya:
- Mengharuskan variabel tersebut pasti ditetapkan sebelum mengizinkan alamat operator untuk digunakan pada variabel tersebut.
- Membuat mengambil alamat variabel yang tidak diinisialisasi tidak aman.
Examples:
static void SkipInit<T>(out T value)
{
// value is considered definitely assigned after the address-of
fixed (void* ptr = &value);
}
int i;
// i is considered definitely assigned after the address-of
_ = &i;
// Incrementing whatever was on the stack
i++;
nilai MemorySafetyRulesAttribute
Apa yang harus menjadi versi aturan keamanan memori "diaktifkan"/"diperbarui"?
2?
15?
11?
Lihat juga Adopsi SDK Tidak Aman dan Keikutsertaan yang lebih bertahap?.
(dijawab) Keikutsertaan yang lebih bertahap?
Hari ini, memilih ikut serta memberi Anda dua hal sekaligus: penegakan aturan yang tidak aman dalam kode Anda sendiri, dan sinyal yang diterbitkan kepada konsumen Anda (melalui atribut tingkat perakitan) bahwa anotasi Anda disengaja. Mungkin ada pengguna yang ingin mulai mendapatkan diagnostik penegakan saat mereka membuat anotasi, tanpa siap untuk menerbitkan bahwa mereka telah sepenuhnya membuat anotasi rakitan mereka. Haruskah kita memiliki tingkat keikutsertaan "tengah" yang akan menampilkan diagnostik yang tidak aman sebagai peringatan, dan keikutsertaan penuh akan mempromosikannya menjadi kesalahan?
- LDM 2026-04-29: ya, tetapi tidak memblokir untuk pratinjau
(dijawab) Keikutsertaan yang lebih halus
Berikan mekanisme keikutsertaan berbasis wilayah yang terperinci dan dianalogikan dengan mekanisme yang kami buat untuk jenis referensi nullable, di mana pengguna dapat menggunakan direktif untuk mengaktifkan fitur untuk wilayah kode sumber tertentu? Lihat juga Ikut/keluar untuk wilayah kode.
- LDM 2026-04-29: tidak
(dijawab) extern secara implisit tidak aman
Saat ini adalah satu-satunya tempat di mana RequiresUnsafeAttribute disintesis tanpa kata kunci eksplisit unsafe .
Apakah kita baik-baik saja dengan outlier ini?
Selain itu, CoreLib mengekspos banyak metode ekstern (FCalls) sebagai aman saat ini. Memperlakukan metode ekstern karena secara implisit tidak aman akan memerlukan pembungkusan metode ekstern yang tidak aman secara implisit dengan pembungkus yang aman. Kita mungkin mengalami situasi di mana menambahkan pembungkus tambahan sulit karena detail implementasi runtime.
-
LDM 2026-04-01:
externanggota harus secara eksplisit ditandai dengan aman atau tidak aman - LDM 2026-04-06: keputusan yang sama dinyatakan kembali
-
LDM 2026-04-13: keputusan sementara untuk menggunakan
safekata kunci -
LDM 2026-05-13: gunakan
safekata kunci (masih terbuka untuk mengunjungi kembali)
Jawaban: extern anggota harus ditandai baik unsafe atau safe (kami menambahkan kata kunci baru untuk itu, tetapi dapat mengunjunginya kembali sebelum fitur dikirim, berdasarkan umpan balik).
(dijawab) Izinkan safe pada non-anggotaextern (LibraryImport)
Ini bukan pertama kalinya kami mempertimbangkan pertanyaan ini.
Grup kerja awalnya dibahas extern dan LibraryImport anggota bersama-sama.
LDM kemudian mempertimbangkan apakah anggota dengan unsafe blok atau penunjuk dalam tanda tangan mereka harus diharuskan untuk membawa penanda eksplisit safe , dan memutuskan bahwa badan metode analisis tidak memerlukan upacara ini: tidak seperti extern batas, implementasi mereka dapat diperiksa untuk menentukan apakah mereka melepas kewajiban keselamatan mereka.
Akibatnya, safe saat ini hanya diizinkan di mana pilihan keamanan eksplisit diperlukan, seperti pada extern anggota dan bidang dalam tata letak eksplisit.
Tim pustaka sejak itu telah memberikan data baru dari LibraryImport pembuatan sumber yang menjamin untuk mengunjungi kembali pertanyaan tersebut.
LibraryImport adalah bentuk modern yang disukai dari P/Invoke, tetapi apakah implementasi parsial yang dihasilkan adalah extern detail implementasi.
Untuk tanda tangan blittable, generator dapat memancarkan implementasi langsung extern (contoh yang disederhanakan):
// User code
[LibraryImport("kernel32.dll")]
static partial int Blit(int x);
// Generated code
[DllImport("kernel32.dll", EntryPoint = "Blit", ExactSpelling = true)]
static extern partial int Blit(int x);
Deklarasi yang dihasilkan extern harus ditandai safe atau unsafe, dan deklarasi parsial harus menyetujui pengubah keselamatan mereka.
Oleh karena itu, deklarasi yang ditulis pengguna memerlukan pengubah yang sama.
Untuk tanda tangan yang membutuhkan marshalling, generator malah memancarkan pembungkus terkelola di sekitar privat extern:
// User code
[LibraryImport("kernel32.dll")]
static partial int NotBlit(ref int x);
// Generated code
static partial int NotBlit(ref int x)
{
fixed (int* xNative = &x)
{
return __PInvoke(xNative);
}
[DllImport("kernel32.dll", EntryPoint = "NotBlit", ExactSpelling = true)]
static extern unsafe int __PInvoke(int* xNative);
}
Di sini metode parsial yang menghadap pengguna bukan extern, jadi safe tidak diizinkan.
Oleh karena itu, sintaksis pengguna yang tersedia tergantung pada bentuk implementasi mana yang dipilih generator, meskipun pilihan tersebut LibraryImport bukan bagian dari kontrak dan dapat berubah seiring berkembangnya generator.
Selalu menghasilkan pembungkus akan membuat sintaksis konsisten, tetapi akan memikat tanda tangan yang dapat dipancarkan secara langsung dan akan menciptakan perbedaan pengalaman pengguna antara LibraryImport dan DllImport.
Harus safe diizinkan pada setiap deklarasi di mana unsafe dapat menandai anggota memerlukan tidak aman, bahkan ketika tidak diperlukan?
Atau, haruskah bahasa menyediakan aturan yang lebih sempit untuk anggota parsial yang diterapkan oleh generator sumber?
Rekomendasi Grup Kerja: Izinkan safe sebagai pengubah deklarasi di mana saja yang unsafe dapat menandai deklarasi memerlukan-tidak aman.
Jika tidak diperlukan, safe adalah no-op.
Skenario yang perlu memerlukan pengubah eksplisit ketika bahasa tidak, seperti LibraryImport yang menghasilkan non-pembungkusextern , akan memerlukan penganalisis untuk menegakkan keberadaan safe atau unsafe.
-
LDM 2026-07-22: izinkan
safesebagai pengubah deklarasi di mana pun yangunsafedapat menandai deklarasi sebagai memerlukan-tidak aman
(dijawab) unsafe default konteks dalam anggota
Kita dapat mempertimbangkan untuk tidak secara otomatis menjadikan seluruh isi metode sebagai unsafeunsafe konteks. Rust melakukan ini di RFC 2585, dengan motivasi bahwa itu membantu mengurangi cakupan unsafe blok ke lokasi yang unsafe sebenarnya digunakan. Kita bisa melakukan hal yang sama di C#, baik sebagai peringatan atau kesalahan, dengan motivasi serupa.
-
LDM 2026-04-22: ya,
unsafedalam tanda tangan tidak membuat isiunsafe
(dijawab) new() batasan
Apakah kami ingin mendukung new() dengan memerlukan-tidak aman (sesuatu yang saat ini tidak kami dukung di pengkompilasi untuk fitur lain, seperti Obsolete)?
M<C>(); // should be an error outside `unsafe` context since `M` calls the requires-unsafe `C..ctor`?
void M<T>() where T : new()
{
_ = new T();
}
class C
{
unsafe public C() { }
}
-
LDM 2026-05-13: jenis dengan konstruktor tanpa parameter yang tidak aman tidak boleh memenuhi
new()batasan dalam posisi deklarasi (di mana kita tidak memiliki cara untuk memperkenalkanunsafekonteks)
new() batasan dan usings
Bagaimana seharusnya berulah dalam alias dan penggunaan statis?
- Jika itu adalah kesalahan pada
usingdeklarasi, ditekan melalui kata kunci yangunsafesudah kami dukung di sana, atau - haruskah kesalahan biasanya di situs penggunaan seperti jika digunakan langsung tanpa alias atau statis menggunakan?
Note
Dalam kasus kedua, kita perlu menambahkan peringatan "tidak unsafeberarti" untuk menggunakan alias dan penggunaan statis.
class C
{
unsafe public C() { }
}
class D<T> where T : new()
{
public static void M() { _ = new T(); }
}
using X = D<C>;
using unsafe X = D<C>;
X.M();
using static D<C>;
using static unsafe D<C>;
M();
Perhatikan bahwa batasan lain bersifat seperti opsi sebelumnya hari ini:
using X = D<C>; // error here
_ = new X(); // ok
_ = new D<C>(); // error here
class C
{
public C(int x) { }
}
class D<T> where T : new();
Haruskah lebih banyak konstruksi unsafe?
-
dynamic(mungkin harus cocok dengan apa yang diputuskan BCL untuk API refleksi)
(dijawab) Bagaimana melanggar kita ingin condong
Teks pertanyaan
Proposal awal adalah pendekatan yang melanggar secara maksimal, terutama sebagai tes lakmus untuk seberapa agresif yang kita inginkan. Ini mengusulkan tidak ada kemampuan untuk memilih/keluar bagian kode, mengubah arti unsafe pada metode, melarang penggunaan unsafe pada jenis, menggunakan kesalahan alih-alih peringatan, dan umumnya memaksa migrasi untuk terjadi sekaligus, pada saat kompilator ditingkatkan (dan kemudian berpotensi berulang kali sebagai pembaruan dependensi dan menambahkan unsafe ke anggota yang sudah digunakan). Namun, kita memiliki banyak pengalaman dalam membuat perubahan seperti ini yang dapat kita gambar untuk mencakup ukuran break down dan memungkinkan adopsi inkremental. Opsi ini tercakup di bawah ini.
Ikut/keluar untuk wilayah kode
Ini bukan pertama kalinya C# mendefinisikan ulang kasus "dasar" dari kode yang tidak dinotasi. C# 8.0 memperkenalkan fitur jenis referensi nullable, yang dalam banyak hal dapat dilihat sebagai cetak biru tentang bagaimana unsafe fitur membentuk. Ini memiliki tujuan yang sama (mencegah bug yang menagih miliaran dolar dengan mendefinisikan ulang cara C# default ditafsirkan) dan set fitur umum serupa (tambahkan info baru ke jenis untuk menyebarkan status dan menghindari bug). Ini juga sangat melanggar, dan membutuhkan serangkaian fungsionalitas ikut serta menolak yang kuat untuk memungkinkan fitur diadopsi dari waktu ke waktu oleh basis kode. Fungsionalitas itu adalah "konteks jenis referensi nullable". Ini adalah cakupan leksikal yang menginformasikan pengompilasi, untuk wilayah tertentu dalam kode, baik cara menafsirkan referensi jenis yang tidak diannotasi dan jenis peringatan apa yang akan diberikan kepada pengguna. Kita juga dapat menggunakan ini sebagai model unsafe , menambahkan "konteks aturan keselamatan" atau serupa dengan memungkinkan pengendalian apakah aturan baru ini diterapkan atau tidak.
Salah satu keuntungan yang kita miliki dengan fitur baru unsafe adalah bahwa mereka jauh lebih jarang. Meskipun ada jumlah unsafe panggilan yang layak di pustaka teratas, perkiraan kami tentang persentase pustaka teratas yang menggunakan unsafe jauh lebih rendah daripada "setiap baris kode C# yang pernah ditulis". Mudah-mudahan ini berarti bahwa, sementara beberapa kemampuan untuk memilih keluar mungkin diperlukan, kita tidak perlu mekanisme yang rumit seperti yang dimiliki nullable, dengan sakelar praprosesor khusus dan sejenisnya.
Peringatan vs kesalahan
Proposal saat ini menyatakan bahwa persyaratan keamanan memori saat ini diberlakukan melalui peringatan, bukan kesalahan. Ini menarik dari pengalaman kami bekerja dengan fitur nullable, di mana peringatan memungkinkan basis kode untuk secara bertahap mengadopsi fitur baru dan tidak perlu mengonversi swathes besar kode sekaligus. Kami mengharapkan proses serupa akan diperlukan untuk peringatan yang tidak aman: banyak basis kode hanya akan dapat mengaktifkan aturan baru secara global dan melanjutkan hidup mereka. Tetapi kami mengharapkan basis kode yang paling kami pedulikan untuk mengadopsi aturan baru akan memiliki sejumlah besar kode untuk dianotasi, dan kami ingin mereka dapat bergerak maju dengan fitur, daripada melihat dinding kesalahan dan segera menyerah. Dengan membuat peringatan persyaratan, kami mengizinkan basis kode ini untuk memperbaiki peringatan file demi file atau metode demi metode sesuai kebutuhan, menonaktifkan peringatan di tempat lain.
Pemisah tanda tangan metode
Saat ini, kami mengusulkan bahwa unsafe sebagai kata kunci pada metode berpindah dari sesuatu yang dilingkup secara leksikal tanpa dampak semantik ke sesuatu yang memiliki dampak semantik, dan tidak terlingkup secara leksikal.
Kita dapat membatasi jeda ini dengan memperkenalkan kata kunci baru ketika pemanggil metode atau anggota harus dalam unsafe konteks; misalnya, callerunsafe sebagai pengubah.
Default untuk generator sumber
Untuk nullable, kami memaksa pembuat generator untuk secara eksplisit ikut serta ke nullable terlepas dari apakah seluruh proyek telah memilih ke fitur secara default, sehingga output generator tidak rusak oleh pengguna yang menyalakan nullable dan memperingatkan sebagai kesalahan. Haruskah kita melakukan hal yang sama untuk generator sumber?
Conclusion
Dijawab dalam LDM 2025-11-05. Kami akan melaporkan kesalahan untuk masalah keamanan memori ketika aturan baru diaktifkan, dan tidak ada pengecualian untuk generator sumber yang akan dibuat.
(dijawab) Kesalahan atau peringatan?
- LDM 2025-11-05: kesalahan
(dijawab) Ketahanan generator sumber
- LDM 2025-11-05: tidak ada
(dijawab) Memerlukan safe pembuat untuk anggota dengan unsafe blok atau pointer?
- LDM 2026-04-13: tidak
(dijawab) Mode kompat untuk penelepon yang tidak dipilih juga?
- LDM 2026-04-22: ya
- LDM 2026-04-29: tidak ada perubahan untuk pemanggil yang dipilih
- LDM 2026-04-29: tingkat keparahan: kesalahan
Jawaban: anggota yang tidak ikut serta dengan penunjuk dalam tanda tangan dianggap tidak aman untuk memilih penelepon.
(dijawab) Perluas mode kompatasi?
Haruskah kita mempertimbangkan nint dan System.IntPtr sebagai penunjuk juga?
Haruskah kita mempertimbangkan extern/DllImport dari penelepon yang tidak ikut serta karena memerlukan tidak aman juga?
Haruskah kita memiliki peringatan selimut ketika assembly opted-in mereferensikan assembly non-opted-in?
- LDM 2026-04-29: tidak ada ekstensi (penganalisis dapat mencakup beberapa sinyal tidak aman yang kurang pasti)
C# feature specifications