Nem biztonságos evolúció

A bajnokkal kapcsolatos kérdés: https://github.com/dotnet/csharplang/issues/9704

Összefoglalás

A C# definícióját unsafe úgy frissítjük, hogy az olyan helyekre hivatkozik, ahol mutatótípusokat használnak, olyan helyekként, ahol a futtatókörnyezet által nem felügyelt memória dereferens. Ezek a helyek a memória biztonsága szempontjából nem biztonságosak, és felelősek a memóriabiztonsági problémákként kategorizált CVE-k (gyakori biztonsági rések és kitettségek) nagy részének felelősei.

// 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*.
    }
}

Motiváció

Ennek a funkciónak a háttere is megtalálható https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.md, amely nyomon követi a javaslat részeként szükséges szélesebb körű ökoszisztéma-változásokat. Ezek közé tartoznak a BCL-frissítések, hogy megfelelően jegyzetelje a metódusokat, mivel azok nem biztonságosak, valamint eszközfrissítéseket is tartalmaznak, hogy jobban megértsék, hol fordul elő a memóriabiztonság. A C# esetében meg szeretnénk győződni arról, hogy a memória biztonsága nem megfelelő a nyelv szerint; ma nehéz lehet holisztikusan megnézni egy programot, és megérteni az összes helyet, ahol a memória nem biztonságos. Ennek az az oka, hogy a különböző segítők, például a System.Runtime.CompilerServices.Unsafe, System.Runtime.InteropServices.Marshalés mások nem fejezik ki, hogy megsértik a memóriabiztonságot, és különleges figyelmet igényelnek. Az ilyen segítőket használó módszerek nem egyértelműek azonnal, és a memóriabiztonsággal kapcsolatos problémák kódjának naplózásakor (akár a felülvizsgálat előtt, akár a jelentett biztonsági rés okának megállapításakor) nehéz lehet meghatározni azokat a helyeket, amelyek hozzájárulhatnak a problémákhoz.

unsafe A C#-ban korábban egy adott memóriabiztonsági lyukra utalt: a mutatótípusok meglétére. Abban a pillanatban, amikor a mutatótípus már nincs benne, a C# tökéletesen örül, hogy a memória biztonsága nem biztonságos a kódban. Ezzel a problémával szeretnénk foglalkozni a C# és a .NET ökoszisztémájának e fejlődésévelunsafe, megjelölve azokat a területeket, ahol a memóriabiztonság potenciálisan előfordulhat, így a véleményezők és az auditorok könnyebben megérthetik a program lehetséges memóriabiztonságának határait. Fontos, hogy ez azt jelenti, hogy megváltoztatjuk a jelentés jelentését unsafe, nem csak kibővítjük azt. A mutató megléte önmagában nem veszélyes; a nem biztonságos művelet késlelteti a mutatót. Ez tovább terjed a típusokra; típusok nem lehetnek eredendően nem biztonságosak. Csak olyan típus használata, amely nem biztonságos, nem pedig az adott típus megléte.

Annak érdekében, hogy ezek az információk áthaladjanak a rendszeren, módot kell biztosítanunk arra, hogy magukat unsafea metódusokat megjelöljük . Ma, unsafe mivel a módszermódosítónak nincs külső hatása, csak a tagok aláírásában és törzsében használható mutatókat tesz lehetővé. A módosító unsafe valójában nyilvánosan megváltoztatja a tag jelentését; azt jelzi, hogy a tag memóriabiztonsági aggályokkal rendelkezik, és a használatokat a programozónak manuálisan kell ellenőriznie a tag használatával. Ez a következő jelentés meglévő jelentésének unsafekiterjesztése: unsafe a szervezet honosítja az adott szervre vonatkozó ellenőrzési kötelezettséget, míg unsafe az aláírás kiterjeszti ezt a kötelezettséget a hívóra.

Ez a C#-felhasználói bázis egyes szegmensei esetében potenciálisan nagy törést jelent. Reméljük, hogy sok felhasználónk számára ez hatékonyan átlátható, és az új szabályokra való frissítés zökkenőmentes lesz. Mivel azonban egyes nagy API-felületeket, például a tükröződés nagy részeit meg kell jelölni unsafe, úgy gondoljuk, hogy az ökoszisztéma teljes elágaztatásának elkerülése érdekében az új szabályok megfelelő helyszíni felületére lesz szükség.

Kritikus változások

Az alábbi kompatibilitástörő változások figyelhetők meg a nyelvi funkciót implementáló fordítóra való frissítéskor.

  • Ha a frissített memóriabiztonsági szabályok engedélyezve vannak (ez lehet az alapértelmezett vagy akár az egyetlen lehetőség egy jövőbeli .NET verzióban):
    • unsafe egy tagon most már nem biztonságosként jelöli meg, ami azt jelenti, hogy a hívóknak kontextusban unsafe kell lenniük, és a felülbírálások nem lehetnek unsafe , ha az alaptag biztonságos.
    • unsafe egy tagon vagy típuson nem vezet be unsafe automatikusan környezet, ami azt jelenti, hogy explicit unsafe blokkokat kell használni a tagszervezetekben és inicializálókban végzett műveletek köré unsafe .
    • extern azexplicit elrendezésben lévő tagok és mezők explicit unsafe/safe kulcsszót a deklarációban.
    • stackalloc bizonyos feltételek mellett környezetre unsafe van szükség.
    • unsafe a módosító hiba a típusdeklarációk, statikus konstruktorok és destruktorok esetén, mert nincs hatása.
  • Új langversion alatt:
    • A Lambda-következtetés több jelöltet is figyelembe vehet, ami túlterhelés-feloldás kétértelműséget eredményez.
    • safe mostantól egy környezetfüggő kulcsszó, amely megszakíthatja a típusként használt kódot.
    • Az örökölt módban lévő compat mód további hibákat eredményezhet.

Részletes tervezés

Terminológia: a tagoknak nem biztonságos (korábban hívó-nem biztonságos) hívót kell meghívnunk, ha

Szemantika

Ez a javaslat a következőt ismerteti:

Ez az új szintaxis az új LangVersion alatt érhető el, de a bejelentkezéstől függetlenül, azon a helyszínen, amelyet megpróbálunk létrehozni, hogy bármit megtehessünk, amit a jóváhagyáskor el kell végeznie, mielőtt bejelentkezik.

Meglévő unsafe szabályok

A meglévő C#-specifikációnak van egy nagy szakasza, amelynek címe unsafe: §24 Nem biztonságos kód. Feltételesen normatívként van definiálva, mivel a funkció támogatásához unsafe nem szükséges érvényes C#-fordító. A jelenleg feltételesen normatívnak tekintett elemek nagy része már nem lesz így a változás után, mivel a mutatók definíciójának nagy része már nem tekinthető önmagában nem biztonságosnak. A mutatótípusok, a rögzített és áthelyezhető változók, az összes mutatókifejezés (kivéve a mutató indirektcióját, a mutatótagok hozzáférését és a mutatóelem-hozzáférést) és már nem tekinthetők megfixed, és a normál C#-ban léteznek, és nincs szükség a környezetben való használatra.unsafe Hasonlóképpen a rögzített méretű puffer vagy inicializált stackalloc deklarálás is teljesen legális a biztonságos C#-ban. Az összes ilyen esetben csak a nem biztonságos memóriához fér hozzá .

Fontos, hogy ezek a mutatólazítások attól függetlenül érvényesek, hogy egy szerelvény afrissített memóriabiztonsági szabályokat alkalmazza-e. Csak azok a műveletek igényelnek kontextust unsafe , amelyek ténylegesen késleltetik vagy más módon közvetlenül hozzáférnek a memóriához. Ez lehetővé teszi a növekményes migrálási útvonalat: a felhasználók átformálhatják a meglévő kódot úgy, hogy befelé haladnak unsafe , amikor a tag belsőleg kezeli a kockázatot, vagy kifelé, amikor a hívónak részt kell vennie a naplózásban, mielőtt megfordítja a szerelvényszintű opt-in kapcsolót.

Mivel a kódszakasz és a C# egyéb részeinek részletes átírása ebben a unsafe változásban rejlik, nem lenne célszerű, és valószínűleg nem lenne hasznos a specifikáció meglévő szabályainak sorról sorra történő tagolásához. Ehelyett áttekintést adunk az adott szakaszban történő módosításról, valamint konkrét új szabályokat a környezetekben unsafe engedélyezett változásokról.

Nem biztonságos környezeteket igénylő kifejezések újradefiniálása

A következő kifejezések használatához környezet szükséges unsafe :

Ezen kifejezések mellett a kifejezések és az utasítások feltételesen is igényelhetnek kontextust unsafe , ha ezek a jelöléssel ellátott szimbólumoktól függenek unsafe. Ha például nem biztonságos metódust hív meg, a invocation_expression környezetre lesz szükség unsafe . A beágyazott meghívásokkal (például usings és foreachhasonló) rendelkező utasítások kontextust unsafe is igényelhetnek, ha nem biztonságos tagot használnak.

Ha azt mondjuk, hogy "nem biztonságos környezetre van szükség" vagy hasonló a dokumentumban, az azt jelenti, hogy olyan hibát bocsátunk ki, amely miatt a szerkezethez unsafe környezet szükséges.

Note

Ennek a szakasznak valószínűleg ki kell terjednie ahhoz, hogy formálisan deklarálja, mit kell figyelembe vennie az egyes kifejezéseknek és utasításoknak, hogy kontextust unsafe igényeljenek.

Mutatótípusok

Ahogy említettük, a mutatók már nem lesznek eleve veszélyesek. A 24.3. A mutatótípusok normál C#-ban léteznek, és nem szükséges unsafe őket létrehozni. A típusdefiníciókat a 8.1.

Hasonlóképpen, a mutatók konvertálásáta §10-be kell dolgozni, és a környezetekre mutató hivatkozásokat unsafe el kell távolítani.

Hasonlóképpen, a mutatókifejezéseket a közvetett mutató, a mutatótag-hozzáférés és a mutatóelem-hozzáférés kivételével a §12-be kell dolgozni, és a környezetekre mutató hivatkozásokat unsafe el kell távolítani. Ezeknek a kifejezéseknek a jelentésében nem változik szemantika; az egyetlen változás az, hogy már nem igényelnek unsafe környezet használatát.

A mutató közvetett elérése, a mutatótag-hozzáférés és a mutatóelem-hozzáférés esetében ezek az operátorok nem biztonságosak, mivel ezek a futtatókörnyezetet nem kezelő hozzáférési memória. A 24. §-ban maradnak, és továbbra is környezet használatát követelik unsafe meg. A környezeten unsafe kívüli bármely használat hiba. Ezen operátorok szemantikája nem változik; még mindig pontosan ugyanazt jelentik, mint amit ma csinálnak. Ezeknek a kifejezéseknek mindig egy unsafe kontextusban kell lenniük.

A rögzített utasítás a 13. §-ba kerül, és a környezetekre mutató hivatkozásokat unsafe eltávolítja.

A függvénymutatók még nincsenek beépítve a C# fő specifikációjába, de hasonló hatással vannak rájuk; a függvénymutató meghívása ki nem kerül a szabványos specifikációba. A függvénymutató hívási kifejezésének mindig egy unsafe környezetben kell történnie.

Rögzített méretű pufferek

A rögzített méretű pufferek története hasonló a mutatókhoz. A rögzített méretű puffer definíciója önmagában nem veszélyes, és a 16.3. Egy kifejezés rögzített méretű pufferének elérése hasonlóan biztonságos, kivéve, ha a kifejezés egy kifejezés primary_expressionelement_access; a rendszer a fenti szabályoknak megfelelően pointer_element_access ként értékeli ki őket, ami nem biztonságos.

Veremfoglalás

A veremfoglalás története szintén nagyon hasonlít a mutatókhoz. A mutatóvá alakítás stackalloc már nem biztonságos; a mutató halasztása nem biztonságos. Azonban hozzáadunk egy új szabályt:

A stackalloc_expression nem biztonságosak, ha az alábbi állítások mindegyike igaz:

  • A stackalloc_expression átalakítja egy Span<T> vagy egy ReadOnlySpan<T>.
  • A stackalloc_expression nem rendelkezik stackalloc_initializer.
  • A stackalloc_expression egy alkalmazott tagon SkipLocalsInitAttribute belül használja.

Ezekben a környezetekben az eredményül kapott veremterület ismeretlen memóriatartalommal rendelkezhet, és olyan típussá alakul át, amely biztonságos burkolót biztosít a nem felügyelt memóriahozzáférés köré. Ez sérti a szerződését Span<T> , és ReadOnlySpan<T>így ki kell vizsgálni a szerző és a véleményezők az ilyen kód.

Ellentétben a többi olyan módosítással unsafe , amely enyhíti a szabályokat, ez egy szigorítás, és ezért csak a frissített memóriabiztonsági szabályokra vonatkozó jóváhagyás mellett vonatkozik a szünet elkerülése érdekében.

Note

Ez azt jelenti, hogy a mutató hozzárendelése stackalloc a környezettől függetlenül mindig biztonságos.

Vegye figyelembe, hogy egy stackalloc felügyelt típus továbbra is hiba marad.

sizeof

Bizonyos előre definiált típusok sizeof esetében mindig állandó és biztonságos volt (12.8.19.§), és ez nem változott. Más típusok esetében a nem biztonságos környezet megkövetelésére szolgál (sizeof), de most már biztonságos, függetlenül attól, hogy a frissített memóriabiztonsági szabályokra van-e szükség.

Felülírás, öröklés és megvalósítás

Memóriabiztonsági hiba a tag szintjén hozzáadni unsafe egy olyan tag felülbírálásában vagy implementálásában, amely eredetileg nem igényel nem biztonságosat , mert előfordulhat, hogy a hívók az alapdefiníciót használják, és nem látják a származtatott implementációk hozzáadását unsafe .

Delegátusok és lambdák

Memóriabiztonsági hiba, ha egy nem biztonságos tagot delegált típussá alakít át a unsafe környezeten kívül. A delegálási típusok és függvénytípusok nem igényelhetnek nem biztonságos elemet. Ez egy fordítási idő típusú hiba, amelyet a lambda szimbólumon kell alkalmazni unsafe .

extern

Mivel extern a metódusok olyan natív helyekre vonatkoznak, amelyeket a futtatókörnyezet nem tud garantálni, a fordító nem tudja megállapítani, hogy biztonságosak vagy nem biztonságosak-e. A C# nem tud biztonságosan meghívni olyan metódusokat is, amelyek csak érték szerint vesznek fel unmanaged paramétereket, mivel a metódushoz használt hívási konvenció helytelenül adható meg a felhasználó számára, és felülvizsgálattal manuálisan kell ellenőrizni.

Ezért a frissített memóriabiztonsági szabályok értelmében a fordító minden extern metódust explicit módon unsafesafevagy .

extern az örökölt memóriabiztonsági szabályokat használó szerelvények metódusai nem tekinthetők implicit módon unsafe , mert extern olyan megvalósítási részletnek minősülnek, amely nem része a nyilvános felületnek. extern nem garantált, hogy a referenciaszerelvényekben megőrződik.

Vegye figyelembe, hogy ez eltér az örökölt szabályok szerelvényekre is érvényes compat módtól , mivel az aláírással ellátott mutatókkal rendelkező metódusoknak mindig nem biztonságos környezetre lenne szükségük a hívási helyen.

Nem biztonságos módosítók és környezetek

A nem biztonságos környezet specifikációja szerint ma lexikális módon viselkedik, unsafe és a blokk által unsafe tartalmazott teljes szövegtörzset kontextusként unsafe jelöli meg (az iterátortestek kivételével), deklarációk esetén pedig néhány környező kontextust is:

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
}

A frissített memóriabiztonsági szabályok unsafe engedélyezésével egy tag nem biztonságosként jelöli meg, kiterjeszti a naplózási kötelezettséget a hívóra, és nem vezet be kontextust unsafe (ehelyett csak a törzs explicit unsafe régiói hoznak létre unsafe kontextusokat).

unsafe a következő deklarációk hibát eredményeznek, mert már nincs jelentése:

  • delegate,
  • statikus konstruktor,
  • Destruktor
  • típusdeklaráció (classstb struct.).

unsafe a konstruktor az unsafe inicializálóján belül egy környezetet vezet be, azaz a unsafe konstruktor meghívhat egy nem biztonságosbase vagy this konstruktort.

A paraméter nélküli , nem biztonságos konstruktorokkal rendelkező típusok nem felelnek meg a kényszernek new() . Hasonlóképpen, a paraméter nélküli , nem biztonságos konstruktorokkal rendelkező szerkezetek nem felelnek meg a kényszernek struct .

unsafe egy tagra nem vonatkozik a tagon belüli beágyazott névtelen vagy helyi függvény. Ugyanez vonatkozik a blokkon belül unsafe deklarált névtelen és helyi függvényekre is (ezek még mindig olyan unsafe környezetben vannak, mint mindig, de nem lesznek biztonságosak). Ha egy helyi függvényt nem biztonságosként szeretne megjelölni, manuálisan kell megjelölni unsafe. A Lambdas nem jelölhető meg nem biztonságosnak (a unsafe kulcsszó nem engedélyezett rajtuk).

Ha egy tag , partialmindkét résznek meg kell egyeznie a unsafe módosító, változatlan a C# szabályok ma.

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
}

A tulajdonságok get és set/init tartozékok egymástól függetlenül deklarálhatók unsafe; a teljes tulajdonság unsafe megjelölése azt jelenti, hogy mind a tartozékok, mind a getset/init tartozékok nem biztonságosak. Jelenleg nem lehet módosítókat elhelyezni az esemény kiegészítőire, és ez a javaslat nem változtat azon, hogy addremove az esemény tartozékai nem deklarálhatók unsafeegymástól függetlenül. Csak akkor, ha a teljes eseményt megjelöli, unsafeaz azt jelenti, hogy a tartozékok nem biztonságosak, ellenkező esetben biztonságosak.

Fields

unsafe egy mezőn szintén nem biztonságosként jelöli meg, és nem tartalmaz környezetet unsafe az inicializálóban. A compat mód a mezőkre is vonatkozik.

A tulajdonság vagy esemény unsafe megjelölése nem teszi biztonságossá a háttérmezőt.

Egy vagy több példánymezőt tartalmazó [StructLayout(LayoutKind.Explicit)] típusban az összes példánymezőt meg kell jelölni vagy [ExtendedLayout]safe.unsafe Ha a mező "rejtett" egy automatikus tulajdonság vagy mezőszerű esemény mögött, akkor a safe/unsafe követelményt inkább az automatikus tulajdonságra vagy mezőszerű eseményre helyezi át.

Metadaták

Amikor egy szerelvényt lefordítanak az új memóriabiztonsági szabályokkal, a rendszer megjelöli MemorySafetyRulesAttribute (az alábbiakban részletezve), kitöltve a nyelvi verzióval 15 . Ez a jelzés minden alsóbb rétegbeli felhasználó számára, hogy a közgyűlésben meghatározott tagok megfelelően lesznek hozzárendelve RequiresUnsafeAttribute (az alábbiakban részletezve), ha unsafe egy környezet szükséges a meghívásukhoz. Az ilyen közgyűlés azon tagja, aki nincs megjelölve RequiresUnsafeAttribute , nem követeli unsafe meg a környezet meghívását, függetlenül attól, hogy milyen típusúak a tag aláírása.

Hiba a forrásban lévő szimbólumok vagy MemorySafetyRulesAttribute szimbólumok alkalmazásakorRequiresUnsafeAttribute.

A fordító figyelmen kívül hagyja a -megjelölt tagokat RequiresUnsafeAttributeaz örökölt memóriabiztonsági szabályokat használó szerelvényekből (ehelyett a compat módot használja ott).

Ha nem típusú tagként unsafevan megjelölve, a fordító metaadatokban szintetizál egy RequiresUnsafeAttribute alkalmazást a tagon. Ha egy felhasználóhoz tartozó felhasználó számára nem biztonságos tag rejtett tagokat hoz létre, például egy automatikus tulajdonság beolvasási/beállítási módszereit, a felhasználóval szemben álló tag és az adott felhasználó által létrehozott rejtett tagok mindegyike nem biztonságos, és RequiresUnsafeAttribute mindegyikre érvényes.

A MemorySafetyRulesAttribute definíciót és RequiresUnsafeAttribute a definíciót szükség esetén a fordító szintetizálja a szabványos, jól ismert tagszabályok szerint.

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

Compat mód

Az új szabályok engedélyezésekor felmerülő hamis negatív értékek számának csökkentése érdekében az új szabályokra nem frissített modulok tartalékszabálya van. Az ilyen modulok esetében a tag nem biztonságos , ha a paramétertípusok vagy a visszatérési típusok között valahol mutatót vagy függvénymutató-típust tartalmaz (nem mutató típusba ágyazható, például int*[]). Vegye figyelembe, hogy ez nem vonatkozik a kényszertípusok (például ) mutatóira, where T : I<int*[]>mivel azoknak korábban a hívási helyeken sem lenne szükségük nem biztonságos környezetre.

Ez nem tartalmazza a helyettesítő általános paramétereket (például a metódust I<T>.M(T) , ha helyettesítve Tint*[]van ) mivel a céltagnak nincs típusbiztos módja arra, hogy ezt a mutatótípust bármire is használja.

Az ilyen kompat mód megköveteli, hogy a nem biztonságos tagok unsafe olyan környezeteket használjanak, amelyek még a frissített memóriabiztonsági szabályokat nem választó hívóktól is szükségesek. Ez elkerülheti a "lemerülést", amikor a LangVersion frissítése (de a memóriabiztonsági szabályok verziójának frissítése nélkül) biztonságossá teszi a legtöbb mutatóműveletet (beleértve a kódban lévő mutatókkal rendelkező függvények meghívását is, amelyek valószínűleg nem biztonságosként lesznek megjelölve a frissített szabályok kiválasztásakor), és így a kód kevésbé lesz védve ebben az áttelepítési ablakban.

VB

Nem kell támogatást adnunk a Visual Basic a nem biztonságos tagok számára, mivel a VB-ben jelenleg nincsenek unsafe környezetek, és ott sem lehet dolgozni a mutatókkal.

unsafe Kifejezések

A unsafe kifejezések minimális unsafe kontextust vezetnek be egyetlen kifejezés kiértékeléséhez. Olyan helyzetekben hasznos, amikor a unsafe blokkok szükségtelenül szélesítenék a unsafe hatókört, vagy ha unsafe a blokkok nem használhatók szintaktikailag (például szűrőkben, mező inicializálókban, konstruktor inicializálókban és az operandus pozíciójában catchawait).

Szemantika

A unsafe_expressionrendszer primary_no_array_creation_expression ad hozzá egy műveletet:

unsafe_expression
    : 'unsafe' '(' expression ')'
    ;

Ugyanaz a AllowUnsafeBlocks követelmény vonatkozik rá, mint máshol a unsafe kulcsszóra.

Szemantika

Egy unsafe_expression kontextust hoz létre unsafe a kifejezés kiértékeléséhez. Ez azt jelenti, hogy a mutató hareferenciái, a függvénymutató-hívások és a nem biztonságos tagok meghívása mind engedélyezett a zárt kifejezésben. A kifejezés típusa és értéke unsafe_expression a zárt kifejezés típusa és értéke.

Az unsafe an unsafe_expression által létrehozott környezet nem terjed ki a záró zárójelen.

Motivációs és migrálási példák

Számos szintaktikai pozíció egyáltalán nem ismer el unsafe blokkokat, de tartalmazhatnak olyan alexpressziókat, amelyek nem biztonságos tagokat igényelnek . Kifejezések nélkül unsafe az ilyen kód áttelepítéséhez ki kell nyerni a nem biztonságos alkifejezést egy segéd helyi függvénybe vagy egy ideiglenes változóba, amely elhomályosítja a szándékot, és növeli a részletességet.

await nem biztonságos metódust igényel . Egy await kifejezés nem jelenhet meg egy blokkban unsafe . Ha a várt metódus nem biztonságossá válik, ideiglenes változóra van szükség a feladat tárolásához, mielőtt meg lehetne várni:

// Without unsafe expressions: must spill to a temporary
Task t;
// SAFETY: Discharges obligations because reasons
unsafe { t = DoWork(); }
await t;

// With unsafe expressions: the unsafe context wraps only the call;
// the await remains outside it and is fully legal
// SAFETY: Discharges obligations because reasons
await unsafe(DoWork());

Fogási szűrők. A when záradék szűrője catch kifejezés, nem utasítástörzs. A unsafe blokkok csak az utasításokat veszik körül, így nincs hely arra, hogy csak a szűrőkifejezés köré helyezzen egyet. Emellett, ha a try törzs kifejezéseket await tartalmaz, a unsafe blokkok sem tudják körülvenni a teljes try/catch utasítástawaitunsafe a blokkon belül nem engedélyezett. Ha egy szűrőben használt módszer nem biztonságossá válik, a kifejezések nélküli unsafe egyetlen alternatíva egy segítő:

// 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)))
{
}

Mező inicializálók. A frissített szabályok unsafe szerint egy mező nem tartalmaz kontextust unsafe az inicializálóban. Ha egy mező inicializálója nem biztonságos tagot hív meg, a unsafe kifejezés a környezetet úgy adja meg, hogy nem igényel segédmetódust:

// 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());

Konstruktor inicializálók. this(...) és base(...) az inicializáló argumentumlistái kifejezések, nem utasítástörzsek. Ha az argumentumok egyike nem biztonságos tagot hív meg, ismét nincs helye blokk beszúrására unsafe , ezért a hívást máskülönben egy segítőhöz kell áthelyezni:

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()))
    {
    }
}

Beágyazott hívások. Általánosságban elmondható, hogy a csak a nem biztonságos alkifejezések körbefuttatása szigorúan tartja a naplózási hatókört, és elkerüli, hogy biztonságos kódot (például argumentum-kiértékelést vagy egy metódushívást) húzzon a unsafe környezetbe:

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);

Kérdések

(válasz) Nem RequiresUnsafeAttribute tagok jelölésére használható

Ahelyett, hogy a unsafe tagon lévő kulcsszót használnánk a nem biztonságos tagok megjelölésére, használhatnánk egy tagra alkalmazott attribútumot (RequiresUnsafeAttributeés nem módosíthatnánk a módosító jelentését a unsafe tagokon).

A következők előnyei unsafe:

  • más nyelvekhez hasonló, ezért könnyebben érthető,
  • jobban felderíthető, mint egy attribútum.

Egy attribútum (vagy egy másik kulcsszó) előnyei:

  • elkerüli a meglévő tagok unsafefeltörését,
  • növekményes bevezetés lehetséges (tagonként),
  • nem kényszeríti az egész test unsafe megjelölését (még a kulcsszóval unsafe is ,unsafe hogy nem befolyásolhatjuk a testeket),
  • lehetővé teszi az összes nem biztonságos hiba letiltását anélkül, hogy magát a tagot kötelezően nem biztonságosként kellene megjelölnie (példák).

Viták:

Válasz: kulcsszóval unsafe jelöljön meg nem biztonságos tagokat.

Helyi függvények/lambda biztonságos környezetek

Jelenleg unsafe egy metódus törzse lexikális hatókörrel rendelkezik. Minden beágyazott helyi függvény vagy lambda örökli ezt, és a testük nem biztonságos memóriában van. Ez a viselkedés, amit meg szeretnénk tartani a nyelven? Vegye figyelembe, hogy ha megtartjuk unsafe a hívó nem biztonságos állapotának felfedéséhez használt módosítót, az hatással lehet a metódus aláírására. A jelenleg javasolt módon a beágyazott névtelen és helyi függvények nem tartják meg a tagjuk nem biztonságos környezetét.

Delegálás típusa unsafety

Megengedhetjük, hogy a delegált típusok és a lambdas (és függvénytípusok) megjelölése ne legyen biztonságos. Ehhez több további szabályra lenne szükség (külső unsafe környezet):

  • nem engedélyezi a nem biztonságos meghatalmazottak használatát típusargumentumként,
  • tiltsa le a meghatalmazottak átalakítását olyanra, amely nem igényel nem biztonságos (DelegateésExpressionobject ) e nélkül fennáll a veszélye annak, hogy a széljegyzeteket rossz helyen kényszerítikunsafe, és olyan területtel rendelkeznek, ahol a ty valós területe unsafenem megfelelő.

Lambda/metóduscsoport konvertálása biztonságos delegálási típusokra

Ha engedélyezzük unsafe a lambdákat és a meghatalmazottakat, szükség esetén a nem biztonságos lambda vagy metóduscsoport átalakítása nem kötelező,nem biztonságos delegált típusra, amely figyelmeztetés vagy hiba nélkül engedélyezhető egy unsafe környezetben? Ha ezt nem tesszük meg, akkor az elég fájdalmas lehet az ökoszisztéma különböző részeire, különösen a LINQ-lekérdezéseken keresztül továbbított enumerálható elemekre.

Lambda/metóduscsoport természetes típusai

A szemantikára és a kodekre (a további metaadatokon kívül) ma már csak a lambda vagy metóduscsoport function_type kell módosítani, ha unsafe az aláírásban van. Ha el szeretnénk kerülni ezt, akkor nincs valódi hatása sem, ami nagyobb bizalmat adhat az örökbefogadóknak, hogy a viselkedés nem változott finoman a motorháztető alatt.

Note

Ha úgy döntünk, hogy unsafe megtartjuk a lambdas lehetőségét, frissíteni kell ezt a javaslatot, hogy szintaxismódosítást is belefoglaljunk a lambdas deklarálásához unsafe .

Mutatók felügyelt típusokhoz

A C# 11 figyelmeztetéssel teszi lehetővé a mutatók számára a felügyelt típusok használatát. Enyhítsük a figyelmeztetést a műveletek címére? Úgy gondoljuk, hogy a probléma csak akkor jelentkezik, ha a felhasználó elhalasztja az ilyen mutatót, amely a normál nem biztonságos evolúciós szabályok alá tartozik. De mi a helyzet sizeof?

stackalloc inicializálva

A specifikáció ma mindig nem inicializáltnak tekinti stackalloc a memóriát, és azt mondja, hogy a tartalom nincs meghatározva, kivéve, ha manuálisan törölve vagy hozzárendelve van. Tekintjük ezt a hibát, vagy módosítani unsafe kell a megfontolandó szempontokat stackalloc ?

stackalloc szabály

Az LDM-nek meg kell erősítenie a stackalloc fent definiált szabályt , és azt, hogy a jóváhagyástól függetlenül kell-e alkalmazni, mint a többi mutatóval kapcsolatos módosítást.

AllowUnsafeBlocks

AllowUnsafeBlocks A szó jelentése jelenleg változatlan – a kulcsszó vagy truea unsafe kulcsszó használatához be kell állítaniSkipLocalsInitAttribute. Ne követeljük meg a frissített szabályok szerint, SkipLocalsInitAttribute mivel a BCL ezt az attribútumot nem biztonságosként jelölheti meg? A kulcsszóhoz safe is szükség van rá? Szükséges-e mind a blokkokhoz, unsafe mind unsafe a tag deklarációkhoz, vagy ezek más kombinációjához?

(válasz) unsafe Kifejezések

Más, átfogóbb unsafe funkciókkal rendelkező nyelvek olyan kifejezésként lettek hozzáadva unsafe , amely javítja a felhasználói ergonómiát, és lehetővé teszi a szerzők számára, hogy pontosabban korlátozzák a használat helyét unsafe . Ez olyasmi, amit C#-ban szeretnénk? Fontolja meg a közvetlen biztonságot kezelő tag beágyazott hívását unsafe : a szerzőnek most vagy egy unsafe blokkba kell csomagolnia a teljes utasítást, ki kell bővítenie a unsafe környezet hatókörét, vagy a belső függvényhívást egy köztes változóra kell bontania.

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);

Válasz: igen. Tekintse meg a részletes tervezési szakaszt.

További unsafe környezetek és relaxációk

Lazítsunk-e az iterátorok és az aszinkron metódusok unsafe további korlátozásait és mutatóparamétereit? Különösen hasznos lenne a engedélyezése await UnsafeMethod() , mert most a felhasználóknak át kell írniuk ezt a fájlba Task t; unsafe { t = UnsafeMethod(); } await t;. További részletekért lásd: ref/unsafe in iterators/async .

Biztonságos környezetben is engedélyeznünk &UnsafeMethod kell? A javaslat jelenlegi állása szerint ez kontextust igényel unsafe , ha a metódust a rendszer megjelöli unsafe. Mivel azonban csak a címét kapjuk meg, amelynek kontextusra van szüksége unsafe a dereferenced/called esetén, lehetővé tehetjük, hogy a cím egy biztonságos környezetben legyen.

Nem biztonságos relaxációk a LangVersion-on

Feltétel nélkülivé kell tenni a nem biztonságos környezeti lazításokat a LangVersion-on?

  • LDM 2026-04-06: nem feltételesek a memóriabiztonsági szabályok verziójának
  • TEENDŐ: mi a helyzet a LangVersion-dzsel?

(válasz) unsafe a típusok

Fontolóra vehetjük, hogy egy típus teljes lexikális hatókörét unsafe nem lehet automatikusan kontextussá unsafe tenni, és figyelmeztetni egy típusra unsafe , mert annak nincs értelme.

Válasz: unsafe egy típuson hiba történik (visszajelzés alapján újra meg lehet jeleníteni) a frissített szabályok szerint.

unsafe tartozékokon

A többi már meglévő módosítóval összhangban újonnan engedélyezzük unsafe a tulajdonságkiegészítőket, de az esemény kiegészítőit nem. Ez azt is jelenti, hogy unsafe egy tulajdonság csak egy parancsikon a tartozékaihoz unsafe . Ugyanakkor a partialmódosítóknak meg kell unsafe egyeznie:

partial class C
{
    unsafe partial int P { get; set; } // effectively both `get` and `set` are `unsafe` here
    unsafe partial int P { unsafe get => 0; set { } } // still an error: `unsafe` on `get` doesn't match
}

// similar to this pre-existing behavior:
unsafe partial class D
{
    unsafe partial void M();
}
unsafe partial class D
{
    partial void M() { } // error about missing `unsafe`
}

Lehet, hogy ugyanúgy kell viselkednie readonly, mint a tulajdonság és a tartozék egyidejű letiltása unsafe :

partial struct S
{
    readonly partial int P { get; set; }

    // error: Both partial member declarations must be readonly or neither may be readonly
    partial int P { readonly get => 0; set { } }

    // error: Cannot specify 'readonly' modifiers on both property or indexer 'S.P2' and its accessor. Remove one of them.
    readonly int P2 { readonly get => 0; set { } }
}

(válasz) A peremhálózati esetekkel kapcsolatos nem biztonságos hibák letiltásának engedélyezése

Hogyan engedélyezzük a nem biztonságos hibák letiltását a következő forgatókönyvekben?

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

A nem biztonságos hibák elfojtásához valahogy be kell vezetnünk a unsafe tagok aláírásának kontextusát. A unsafe kulcsszó azonban unsafe nem vezet be kontextust. Bevezethetnénk unsafe egy kontextust unsafe az aláírásban, de a konstruktorok kényszerítése nem biztonságos , ha egy alaphoz szükséges-nem biztonságos konstruktort szeretnénk meghívni, szerencsétlennek tűnik. Nem biztonságos használatot követelhetünk meg az aláírásokkal kapcsolatos figyelmeztetésekben, amelyeket a felhasználók helyben is letilthatnak, ha elérik ezeket a ritka peremhálózati eseteket.

A típusokkal kapcsolatban hasonló probléma merül fel, mivel unsafe ezeknél sem vezetnek be unsafe kontextust:

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:
    • a nem végrehajtható kódnak( például az attribútumalkalmazásnak) nem lehet nyomasztó hiba (amíg nem hallunk visszajelzést)
    • más peremhálózati esetek, például new() továbbra is hiba maradnak, amíg nem hallunk visszajelzést
    • ne tiltsa le a paraméter nélküli, nem biztonságos konstruktorok deklarálását
    • csak a unsafe konstruktor inicializálója hívhat meg nem biztonságosbase vagy this konstruktort

params gyűjtemények

class C
{
    unsafe public C() { } // declaring requires-unsafe constructor
}

class B
{
    public void M(params C c) { }
}

Ez a deklaráció egyszerűen engedélyezhető, mivel az ilyen metódus meghívásához mindenképpen szükség van egy unsafe környezetre, mert a nem biztonságos params gyűjtemény a hívási helyen van létrehozva. Bár a deklaráció hatékonyan nem biztonságos, így szükség lehet a unsafe megjegyzésre, vagy legalább figyelmeztetni erre a tényre. Vegye figyelembe, hogy a params gyűjtemény esete ma hiba, összhangban a többi hasonló funkció viselkedésével (Obsolete), UnmanagedCallersOnlyde ez implementációs hiba lehet.

Jól ismert tagok

A megvalósítás egyszerűsége és józansága érdekében javasoljuk, hogy a fordító szabadon feltételezze, hogy minden ismert tag (például Array.Length) biztonságosnak tekinthető (azaz nem igényel nem biztonságos).

Xml-dokumentumok

A hívók kötelezettségének átadása felelősséggel tartozik annak tisztázása érdekében, hogy mi is ez a kötelezettség. Ezt az xml-dokumentumokban már megjeleníthető elemeken túl kell formálissá tennünk?

A megjelölt unsafe tagoknak megjegyzésekkel kell rendelkezniük, amelyek jelzik, hogy a hívónak mit kell tennie a kód helyességének biztosításához. A dokumentációban való könnyebb áttekinthetőség és a megkülönböztetés megkönnyítése érdekében hasznos lehet egy új XML-dokumentumcímke: <safety />. Várhatóan minden elő-/utófeltétel blokkban lesz elhelyezve <safety> .

Az egyes unsafe blokkok érvelésének dokumentálásához javasoljuk a // SAFETY hasonló megjegyzések használatát. Ezeket is ellenőrizni kell a fordítóval (például van-e valamilyen alapértelmezett figyelmeztetése), vagy hagyja azt egy elemzőre?

unsafe Értelmetlenebb figyelmeztetések

Több deklarációnak kell jelentés nélküli unsafe figyelmeztetést vagy hibát okoznia? Például üres testekkel (vagy extern), stb. Már van egy IDE-elemző a szükségtelen unsafe mégis.

Hiba lehet [ModuleInitializer] unsafe void M() { } , hasonlóan a statikus konstruktorhoz?

(válasz) unsafe Mezők

Ma nincs javaslat unsafe egy területen. Előfordulhat azonban, hogy hozzá kell adnunk, hogy minden olvasás vagy írás egy olyan mezőre legyen megjelölve, amely unsafe egy kontextusban unsafe van. Ez lehetővé tenné számunkra, hogy jobban jegyzeteljük a kódokkal kapcsolatos aggályokat, például:

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];
}

Meg kell jelölnünk az automatikus tulajdonság háttérmezőjét unsafeis?

Ahhoz, hogy összhangban lehessünk a tagok döntésével, jó unsafe lenne, ha egy területen nem vezetnénk be kontextust.unsafe Ha nem biztonságos műveleteket használ a mező inicializálójában, a felhasználó mindig beágyazhatja őket egy metódusba, vagy bevezethetjükunsafe a kifejezéseket.

  • LDM 2026-05-13: a mezőket nem biztonságosunsafemódon lehet megjelölni; az inicializálók nincsenek a unsafe környezetben.

(válasz) Explicit elrendezés

A szerkezetek mezőinek meg kell jelölniük a [StructLayout(Explicit)] jelölést, vagy [ExtendedLayout] kötelezőnek kell lenniük a megjelölésüknek unsafe?

Javaslat: igen.

Explicit elrendezés és háttérmezők

Ha a fordító egy automatikus tulajdonság vagy mezőszerű esemény háttérmezőit szintetizálja egy Explicit/Extended típusban, akkor inkább a tulajdonságon/eseményen kell megkövetelnünk?safe/unsafe Ellenkező esetben a felhasználónak ki kell bővítenie ezeket az automatikus deklarációkat manuális mezőre és burkolótag-deklarációkra. Mi a helyzet egy elsődleges konstruktorparaméterrel, amely lekéri a háttérmezőt? A safe paraméterdeklarációban a módosító és unsafe a módosító is jelenleg nincs engedélyezve.

[Out] és [SkipLocalsInit]

Mivel például a VB nem garantálja a [Out] paraméterek inicializálását, az ilyen paraméterek meghívása [SkipLocalsInit]a C#-ban is figyelembe unsafe vehető. Másrészt úgy érzi, hogy a hívó problémája, hogy nem tartja fenn a szerződését [Out] (hasonlóképpen sok más szempontból is veszélyes lehet).

Ha úgy döntünk, hogy ezeknek az eseteknek kell lenniük unsafe, kizárhatjuk azokat a módszereket, amelyeket ismertünk (jelenleg ezek a C# nyelvről származnak, amelyek garantálják a helyes használatot [Out] , de ha más nyelvek implementálják az új szabályokat, akkor ezt is garantálniuk kell).

Nem inicializált változó címének figyelembevétele

Ma, figyelembe véve a címet egy nem feltétlenül hozzárendelt változó lehet figyelembe venni, hogy a változó határozottan hozzárendelt, felfedve nem inicializált tag. Néhány lehetőségünk van a megoldásra:

  1. A változók hozzárendelésének megkövetelése, mielőtt lehetővé tenné egy operátor címének használatát rajtuk.
  2. Ne tegye biztonságossá egy nem inicializált változó címét.

Példák:

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++;

MemorySafetyRulesAttribute értéke

Mi legyen az "engedélyezve"/"frissítve" memóriabiztonsági szabályok verziója? 2? 15? 11? Lásd még az SDK nem biztonságos bevezetését és a fokozatosabb jóváhagyást?.

(válasz) Fokozatosabb bejelentkezés?

Ma az engedélyezés két dolgot kínál egyszerre: a nem biztonságos szabályok betartatását a saját kódjában, és egy olyan jelzést, amelyet a felhasználók (egy szerelvényszintű attribútumon keresztül) tesznek közzé, hogy a széljegyzetek szándékosak. Előfordulhat, hogy vannak olyan felhasználók, akik széljegyzetek nélkül szeretnék megkapni a kényszerítési diagnosztikát anélkül, hogy készen állnának a közzétételre, hogy teljes mértékben jegyzetelték a szerelvényüket. Van-e "középső" jóváhagyási szintünk, amely figyelmeztetésként jelenik meg a nem biztonságos diagnosztikában, és a teljes opt-in a hibákra ösztönözné őket?

(válasz) Finomabb szemcsés bejelentkezés

Adjon meg egy részletes, régióalapú engedélyezési mechanizmust, amely a null értékű referenciatípusokhoz készült, ahol a felhasználók irányelvekkel engedélyezhetik a funkciót a forráskód adott régióiban? Lásd még: A kódrégiók letiltása/letiltása.

(válasz) extern implicit módon nem biztonságos

Jelenleg ez az egyetlen hely, ahol RequiresUnsafeAttribute explicit unsafe kulcsszó nélkül van szintetizálva. Rendben van ez a kiugró érték?

Emellett a CoreLib számos extern metódust (FCalls) is biztonságosként tesz elérhetővé. Ha az extern metódusokat implicit módon nem biztonságosként kezeli, az implicit módon nem biztonságos extern metódusokat biztonságos burkolóval kell burkolni. Előfordulhat, hogy olyan helyzetekbe ütközünk, amikor az extra burkoló hozzáadása a futtatókörnyezet implementálási részletei miatt nehéz.

  • LDM 2026-04-01: extern a tagokat kifejezetten biztonságos vagy nem biztonságosként kell megjelölni
  • LDM 2026-04-06: ugyanez a határozat megismételve
  • LDM 2026-04-13: ideiglenes döntés a kulcsszó használatára safe
  • LDM 2026-05-13: használjon safe kulcsszót (továbbra is nyitva van az újramegjelenítéshez)

Válasz: extern a tagokat meg kell jelölni vagy unsafesafe (új kulcsszót adunk hozzá, de a visszajelzések alapján a funkció előtt újra meg lehet keresni).

Engedélyezés safe nemextern tagokon (LibraryImport)

Nem ez az első alkalom, hogy megfontoljuk ezt a kérdést. A munkacsoport eredetileg tárgyalt extern , és LibraryImport a tagok együtt. Az LDM ezután mérlegelte, hogy az aláírásukban blokkokkal vagy mutatókkal rendelkező unsafe tagoknak kötelező-e explicit safe jelölőt hordaniuk, és úgy döntöttek , hogy az elemezhető módszertesteknek nincs szükségük erre a ceremóniára: a extern határtól eltérően implementációik megvizsgálhatók annak megállapításához, hogy teljesítik-e a biztonsági kötelezettségeiket. Következésképpen jelenleg csak akkor engedélyezett, safe ha explicit biztonsági választásra van szükség, például a tagokra extern és az explicit elrendezésű mezőkre.

A kódtárak csapata azóta olyan új adatokat adott meg a forrásgenerálásbólLibraryImport, amelyek indokolják a kérdés újbóli megválaszolását. LibraryImport a P/Invoke előnyben részesített modern formája, de hogy a generált részleges implementáció implementálási extern részlet-e. Egy titkos aláírás esetében a generátor közvetlen extern megvalósítást tud kibocsátania (leegyszerűsített példák):

// 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);

A létrehozott deklarációt extern meg kell jelölni safe , vagy unsafea részleges deklarációknak meg kell egyezniük a biztonsági módosítójukkal. A felhasználó által létrehozott deklarációnak ezért ugyanazt a módosítót kell megadnia.

A rendezést igénylő aláírások esetében a generátor ehelyett egy felügyelt burkolót bocsát ki egy privát 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);
}

Itt a felhasználó által használt részleges metódus nem extern, ezért safe nem engedélyezett. A felhasználó elérhető szintaxisa ezért attól függ, hogy a generátor milyen megvalósítási formát választ, annak ellenére, hogy ez a választás nem része a LibraryImport szerződésnek, és a generátor fejlődésével változhat. A burkoló mindig konzisztenssé tenné a szintaxist, de pesszimizálná a közvetlenül kibocsátható aláírásokat, és felhasználói élménybeli különbséget hozna létre az és DllImporta közöttLibraryImport.

Minden olyan nyilatkozaton engedélyezni kell safe , ahol unsafe a tag megjelölése nem biztonságos, még akkor is, ha nincs rá szükség? Azt is megteheti, hogy a nyelv szűkebb szabályt biztosít a forrásgenerátorok által implementált részleges tagok számára?

Munkacsoport ajánlása: A deklaráció módosítóként történő engedélyezés safe bárhol, amely unsafe megjelölhet egy deklarációt , nem biztonságos. Ha ez nem kötelező, safe akkor egy no-op. Azok a forgatókönyvek, amelyeknek explicit módosítót kell megkövetelniük, ha a nyelv nem, például LibraryImport nem burkolót generálextern , egy elemzőre lesz szükség a jelenlét safe kényszerítéséhez vagy unsafe.

(válasz) unsafe a tagok környezeti alapértelmezései

Fontolóra vehetjük, hogy egy metódus teljes törzsét unsafe nem tesszük automatikusan kontextussá unsafe . Rust ezt az RFC 2585-ben tette, azzal a motivációval, hogy segít csökkenteni a blokkok unsafe hatókörét unsafe a ténylegesen használt helyekre. Ugyanezt a C#-ban is megtehetjük figyelmeztetésként vagy hibaként, hasonló motivációval.

  • LDM 2026-04-22: igen, unsafe az aláírás nem teszi a törzset unsafe

(válasz) new() Megkötés

Támogatni szeretnénk new()a nem biztonságos szolgáltatásokat (amit jelenleg nem támogatunk a fordítóban más funkciókhoz, például 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: a nem biztonságos paraméter nélküli konstruktorokkal rendelkező típusok nem elégítik ki a kényszert new()

new() kényszer és usings

Hogyan viselkedjen aliasokban és statikus használatokban?

  • Ha ez hiba a using deklarációban, akkor az unsafe ott már támogatott kulcsszóval letiltható, vagy
  • kell ez egy hiba általában a használati helyen, mint lenne, ha közvetlenül nélkül alias vagy statikus használata?

Note

A második esetben "értelmetlen unsafe" figyelmeztetést kell hozzáadnunk az aliasok és a statikus használatok használatára.

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();

Vegye figyelembe, hogy a többi korlátozás ugyanúgy viselkedik, mint a korábbi lehetőség:

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();

Több szerkezetnek kell lennie unsafe?

  • dynamic (valószínűleg meg kell egyeznie azzal, amit a BCL a tükröződési API-k esetében határoz)

(válasz) Hogyan szeretnénk elvarrni a törést?

Kérdés szövege

A kezdeti javaslat egy maximálisantörő megközelítés, főként litmus-tesztként, hogy mennyire agresszívek akarunk lenni. Nem javasolja, hogy a kód egyes szakaszait letiltsa, módosítsa a metódusok jelentését unsafe , tiltsa unsafe a típusok használatát, figyelmeztetések helyett hibákat használjon, és általában egyszerre kényszeríti a migrálást a fordító frissítésekor (majd a függőségek frissítésével és a már használatban lévő tagok hozzáadásával unsafe ). Azonban rengeteg tapasztalatunk van az ehhez hasonló módosítások elvégzéséhez, amelyek alapján ki tudjuk terjedni a lebontások méretét, és lehetővé tesszük a növekményes bevezetést. Ezeket a lehetőségeket alább találja.

Kódrégiók letiltása/letiltása

Nem ez az első alkalom, hogy a C# újradefiniálta a nem megadott kód "alap" esetét. A C# 8.0 bevezette a null értékű referenciatípus-funkciót, amely számos szempontból a funkció alakításának tervrajzaként unsafe tekinthető. Hasonló céljai voltak (az alapértelmezett C# értelmezésének újradefiniálásával több milliárd dollárba került hibák megelőzése) és egy hasonló általános funkciókészlet (új információ hozzáadása a típusokhoz az állapotok propagálása és a hibák elkerülése érdekében). Ez is súlyosan megtört, és erős opt-in és opt out funkciókra volt szükség ahhoz, hogy a funkciót a kódbázisok idővel elfogadják. Ez a funkció a "nullable reference type context". Ez egy lexikális hatókör, amely tájékoztatja a fordítót a kód egy adott régiójáról, mind a névtelen típushivatkozások értelmezéséről, mind pedig arról, hogy milyen típusú figyelmeztetéseket kell adni a felhasználónak. Ezt modellként unsafe is használhatjuk, ha "biztonsági szabályok kontextusát" adnánk hozzá, vagy hasonló módon szabályoznánk, hogy az új szabályok alkalmazása folyamatban van-e.

Az új unsafe funkciók egyik előnye, hogy sokkal kevésbé elterjedtek. Bár a legnépszerűbb kódtárakban tisztességes számú unsafe hívás található, a használt unsafe legnépszerűbb kódtárak százalékos aránya sokkal alacsonyabb, mint a "valaha írt C#-kód minden egyes sora". Remélhetőleg ez azt jelenti, hogy bár bizonyos képesség, hogy opt in /out is szükség lehet, nem kell olyan bonyolult mechanizmus, mint nullable van, dedikált előprocesszor kapcsolók és hasonlók.

Figyelmeztetések és hibák

A javaslat jelenleg azt állítja, hogy a memóriabiztonsági követelmények jelenleg figyelmeztetéssel, nem pedig hibával vannak érvényesítve. Ez a null értékű funkcióval kapcsolatos tapasztalatunkból származik, ahol a figyelmeztetések lehetővé tették, hogy a kódbázisok növekményesen fogadják el az új funkciót, és ne kelljen egyszerre nagy kódrészleteket konvertálni. A nem biztonságos figyelmeztetésekhez hasonló folyamatra lesz szükség: sok kódbázis egyszerűen képes lesz globálisan bekapcsolni az új szabályokat, és továbblépni az életükkel. Azt várjuk azonban, hogy az új szabályok bevezetésekor a leginkább fontos kódbázisok nagy mennyiségű kódot tartalmaznak majd, és azt szeretnénk, hogy ahelyett, hogy egy hibafalat látnának, és azonnal feladnának, képesek lesznek továbblépni a funkcióval. A követelményekre vonatkozó figyelmeztetések megadásával lehetővé tesszük, hogy ezek a kódbázisok szükség szerint fájlonként vagy metódusonként kijavítsa a figyelmeztetéseket, és mindenhol letiltsa a figyelmeztetéseket.

Metódus aláírási törése

Jelenleg azt javasoljuk, hogy unsafe a metódus kulcsszójaként lépjen át valami olyanra, amely lexikális hatókörű, szemantikai hatás nélkül olyanra, amelynek szemantikai hatása van, és nem lexikális hatókörű. Ezt a törést korlátozhatjuk egy új kulcsszó bevezetésével arra az időpontra vonatkozóan, amikor egy metódus vagy tag hívójának kontextusban unsafe kell lennie, callerunsafe például módosítóként.

A forrásgenerátorok alapértelmezései

Null értékű esetén kényszerítjük a generátor szerzőit, hogy explicit módon jelentkezzenek be a null értékűre, függetlenül attól, hogy a teljes projekt alapértelmezés szerint a funkciót választotta-e, hogy a generátor kimenetét ne törje meg a felhasználó, ha bekapcsolja a null értéket, és hibaként figyelmeztet. Ugyanezt kell tennünk a forrásgenerátorok esetében is?

Conclusion

Válasz: LDM 2025-11-05. Az új szabályok bekapcsolásakor a memóriabiztonsági problémák hibáit fogjuk jelenteni, és a forrásgenerátorok esetében nem történik kivétel.

(válasz) Hibák vagy figyelmeztetések?

(válasz) Forrásgenerátor megfizethetőség

(válasz) A blokkokkal vagy mutatókkal rendelkező safe tagokhoz szükséges unsafe a készítő?

(válasz) Compat mód a nem választható hívók számára is?

Válasz: Az aláírásban mutató mutatókkal nem rendelkező tagok nem biztonságosnak minősülnek a bejelentkezett hívók számára.

(válasz) Meghosszabbítja a honfitársítási módot?

Érdemes megfontolni és nint mutatóként is?System.IntPtr Érdemes megfontolnunk extern/DllImport a nem választott hívóktól is , hogy nem biztonságosak ? Legyen-e takaró figyelmeztetés, ha egy választott szerelvény nem választható szerelvényre hivatkozik?

  • LDM 2026-04-29: nincs bővítmény (az elemzők bizonyos kevésbé biztonságos jeleket is lefedhetnek)