Onveilige evolutie

Kampioensprobleem: https://github.com/dotnet/csharplang/issues/9704

Overzicht

We werken de definitie van in C# bij van unsafe verwijzingen naar locaties waar aanwijzertypen worden gebruikt, om locaties te zijn waar het geheugen niet wordt beheerd door de runtime. Deze locaties zijn waar onveilig geheugen optreedt en verantwoordelijk zijn voor het merendeel van CVE's (Common Vulnerabilities and Exposures) die zijn gecategoriseerd als problemen met geheugenveiligheid.

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

Motivatie

De achtergrond voor deze functie vindt u ook, https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.mdwaarin de bredere ecosysteemwijzigingen worden bijgehouden die nodig zijn als onderdeel van dit voorstel. Dit zijn BCL-updates voor het correct voorzien van aantekeningen bij methoden als onveilig, evenals hulpprogramma-updates voor een beter inzicht in waar onveilig geheugen optreedt. Voor C# willen we ervoor zorgen dat geheugenonveiligheid correct wordt bijgehouden door de taal; tegenwoordig kan het lastig zijn om een programma holistisch te bekijken en alle locaties te begrijpen waar onveilig geheugen optreedt. Dit komt doordat verschillende helpers zoals de System.Runtime.CompilerServices.Unsafe, System.Runtime.InteropServices.Marshalen anderen niet uitdrukken dat ze geheugenveiligheid schenden en speciale aandacht nodig hebben. Methoden die deze helpers vervolgens gebruiken, zijn niet direct duidelijk en bij het controleren van code voor problemen met geheugenveiligheid (van tevoren tijdens het controleren of bij het vaststellen van de oorzaak van een beveiligingsprobleem dat wordt gerapporteerd) kan het lastig zijn om de locaties vast te stellen die kunnen bijdragen aan problemen.

unsafe In C# wordt in C# verwezen naar een specifiek geheugenveiligheidsgat: het bestaan van aanwijzertypen. Het moment dat een aanwijzertype niet meer betrokken is, is C# perfect blij om geheugenonveiligheid in code te laten laten liggen. Het is dit probleem dat we willen aanpakken met deze evolutie van unsafe C# en het .NET ecosysteem, het labelen van gebieden waar mogelijk onveilig geheugen kan optreden, waardoor revisoren en auditors gemakkelijker inzicht krijgen in de grenzen van potentiële geheugenonveiligheid in een programma. Belangrijk, dit betekent dat we de betekenis van unsafe, niet alleen uitbreiden. Het bestaan van een aanwijzer is zelf niet onveilig; de onveilige actie uitstelt de aanwijzer. Dit strekt zich verder uit tot typen zelf; typen kunnen niet inherent onveilig zijn. Het is alleen de actie van het gebruik van een type dat onveilig kan zijn, niet het bestaan van dat type.

Om deze informatie door het systeem te laten stromen, moeten we daarom een manier hebben om methoden zelf te markeren als unsafe. unsafe Op dit moment heeft een methodeaanpassing geen externe invloed, maar kunnen aanwijzers alleen worden gebruikt in de handtekening en hoofdtekst van het lid. Als unsafe wijzigingsfunctie zal de betekenis van het lid in feite openbaar veranderen. Het geeft aan dat het lid geheugenveiligheidsproblemen heeft en dat alle gebruiksgegevens handmatig moeten worden gevalideerd door de programmeur die het lid gebruikt. Dit is een uitbreiding van de bestaande betekenis van unsafe: unsafe op een lichaam lokaliseert de auditverplichting voor die instantie, terwijl unsafe op een handtekening die verplichting aan de beller wordt uitgebreid.

Dit is mogelijk een grote belangrijke wijziging voor bepaalde segmenten van de C#-gebruikersbasis. Onze hoop is dat dit voor veel van onze gebruikers effectief transparant is en dat het bijwerken naar de nieuwe regels naadloos zal zijn. Gezien het feit dat sommige grote API-oppervlakken zoals grote onderdelen van reflectie mogelijk moeten worden gemarkeerd unsafe, denken we dat er waarschijnlijk een fatsoenlijke on-ramp moet zijn voor de nieuwe regels om volledig bifurcating van het ecosysteem te voorkomen.

Brekende wijzigingen

De volgende belangrijke wijzigingen kunnen worden waargenomen bij het bijwerken naar een compiler die deze taalfunctie implementeert.

  • Als de bijgewerkte regels voor geheugenveiligheid zijn ingeschakeld (dit is mogelijk de standaardinstelling of zelfs de enige optie in een toekomstige .NET versie):
    • unsafe op een lid wordt het nu ook gemarkeerd als onveilig vereist, wat betekent dat bellers zich in een unsafe context moeten bevinden en onderdrukkingen niet kunnen zijn unsafe als het basislid veilig is.
    • unsafe voor een lid of type wordt niet automatisch een unsafe context geïntroduceerd, wat betekent dat expliciete unsafe blokken moeten worden gebruikt rond unsafe bewerkingen in lidorganen en initialisatiemiddelen.
    • extern leden en velden in expliciete indeling vereisen een expliciete unsafe/safe trefwoord in de declaratie.
    • stackalloc onder bepaalde voorwaarden is een unsafe context vereist.
    • unsafe modifier is een fout bij typedeclaraties, statische constructors en destructors, omdat deze geen effect heeft.
  • Onder een nieuwe langversion:
    • Lambda-deductie kan meer kandidaten overwegen, wat resulteert in dubbelzinnigheid over overbelastingsoplossing.
    • safe is nu een contextueel trefwoord waarmee code kan worden verbroken die deze als type heeft gebruikt.

Gedetailleerd ontwerp

Terminologie: we noemen leden onveilig (voorheen bekend als beller-onveilig) als

Syntax

In dit voorstel wordt het volgende geïntroduceerd:

Deze nieuwe syntaxis is beschikbaar onder de nieuwe LangVersion, maar ongeacht de opt-in, onder de veronderstelling dat we het proberen te maken, zodat alles wat u moet doen wanneer u bent aangemeld, u moet doen voordat u zich aanmeldt.

Bestaande unsafe regels

De bestaande C#-specificatie heeft een grote sectie gewijd aan unsafe: §24 Onveilige code. Het is gedefinieerd als voorwaardelijk normatief, omdat het niet vereist is voor een geldige C#-compiler om de unsafe functie te ondersteunen. Veel van wat momenteel als voorwaardelijk normatief wordt beschouwd, zal na deze wijziging niet meer zo zijn, omdat de meeste definitie van pointers niet langer als onveilig op zichzelf wordt beschouwd. Aanwijzertypen, vaste en verplaatsbare variabelen, alle aanwijzerexpressies (met uitzondering van aanwijzer indirectie, toegang tot aanwijzerleden en toegang tot aanwijzerelementen) en de fixed instructie worden allemaal niet meer als beschouwd unsafeen bestaan in normale C# zonder dat ze in een unsafe context hoeven te worden gebruikt. Op dezelfde manier zijn het declareren van een buffer met een vaste grootte of een geïnitialiseerde stackalloc buffer ook perfect legaal in veilige C#. In al deze gevallen heeft het alleen toegang tot het geheugen dat onveilig is.

Belangrijk is dat deze versoepelingen van de aanwijzer van toepassing zijn , ongeacht of een montage zich aanmeldt voor de bijgewerkte regels voor geheugenveiligheid. Alleen bewerkingen die daadwerkelijk deductie of anderszins rechtstreeks toegang tot puntig geheugen vereisen, blijven een unsafe context vereisen. Dit maakt een incrementeel migratiepad mogelijk: gebruikers kunnen bestaande code hervormen door naar binnen te gaan unsafe wanneer het lid het risico intern afhandelt of naar buiten wanneer de aanroeper moet deelnemen aan de audit, voordat ze de opt-in-switch voor de hele assembly spiegelen.

Gezien de uitgebreide herschrijfbewerking van zowel de unsafe codesectie als andere onderdelen die inherent zijn aan deze wijziging, zou het onhandig en waarschijnlijk niet nuttig zijn om een regel-by-line diff van de bestaande regels van de specificatie te bieden. In plaats daarvan geven we een overzicht van de wijziging die in een bepaalde sectie moet worden aangebracht, evenals specifieke nieuwe regels voor wat is toegestaan in unsafe contexten.

Expressies opnieuw definiëren waarvoor onveilige contexten zijn vereist

Voor de volgende expressies is een unsafe context vereist wanneer deze wordt gebruikt:

Naast deze expressies kunnen expressies en instructies ook voorwaardelijk een unsafe context vereisen als ze afhankelijk zijn van een symbool dat is gemarkeerd als unsafe. Als u bijvoorbeeld een methode aanroept die onveilig is, zorgt dit ervoor dat de invocation_expression een unsafe context vereist. Instructies met ingesloten aanroepen (zoals usings, foreachen vergelijkbare) kunnen ook een unsafe context vereisen wanneer ze een onveilig lid gebruiken.

Als we zeggen dat 'een onveilige context vereist' of vergelijkbaar is in dit document, betekent dit dat er een fout wordt verzonden dat voor de constructie een unsafe context moet worden gebruikt.

Note

Deze sectie moet waarschijnlijk worden uitgebreid om formeel aan te geven wat elke expressie en instructie moeten overwegen om een unsafe context te vereisen.

Aanwijzertypen

Zoals vermeld, worden pointers niet meer inherent onveilig. Alle verwijzingen naar onveilige contexten in §24.3 worden verwijderd. Aanwijzertypen bestaan in normale C# en hoeven unsafe ze niet te bestaan. De typedefinities moeten als andere typen worden verwerkt in §8.1 en de volgende secties.

Op dezelfde manier moeten aanwijzerconversies worden gebruikt in §10, waarbij verwijzingen naar unsafe contexten worden verwijderd.

Op dezelfde manier moeten aanwijzerexpressies, met uitzondering van aanwijzer indirectie, toegang tot aanwijzerleden en toegang tot aanwijzerelementen, worden bewerkt in §12, waarbij verwijzingen naar unsafe contexten worden verwijderd. Er verandert geen semantiek over de betekenis van deze expressies; de enige wijziging is dat ze geen context meer nodig hebben unsafe om te gebruiken.

Voor aanwijzer indirectie, toegang tot aanwijzerleden en toegang tot aanwijzerelementen blijven deze operators onveilig, omdat deze toegangsgeheugens die niet worden beheerd door de runtime. Ze blijven in §24 en vereisen nog steeds dat er een unsafe context moet worden gebruikt. Elk gebruik buiten een unsafe context is een fout. Er worden geen semantiek over deze operators gewijzigd; Ze blijven nog steeds precies hetzelfde betekenen als vandaag. Deze expressies moeten altijd in een unsafe context voorkomen.

De vaste instructie wordt verplaatst naar §13, waarbij verwijzingen naar unsafe contexten worden verwijderd.

Functiepointers zijn nog niet opgenomen in de belangrijkste C#-specificatie, maar ze worden op dezelfde manier beïnvloed; alles behalve functie aanwijzer wordt verplaatst naar de standaardspecificatie. Een aanroepexpressie van een functieaanwijzer moet altijd plaatsvinden in een unsafe context.

Buffers met vaste grootte

Het verhaal voor buffers met vaste grootte is vergelijkbaar met aanwijzers. De definitie van een buffer met vaste grootte is zelf niet gevaarlijk en wordt verplaatst naar §16.3. Het openen van een buffer met vaste grootte in een expressie is vergelijkbaar veilig, tenzij de expressie optreedt als de primary_expression van een element_access; deze worden geëvalueerd als een pointer_element_access, wat onveilig is, volgens de bovenstaande regels.

Stacktoewijzing

Nogmaals, het verhaal voor stacktoewijzing is vergelijkbaar met aanwijzers. Het converteren van een stackalloc naar een aanwijzer is niet langer onveilig; het is deductie van die aanwijzer die onveilig is. We voegen echter wel een nieuwe regel toe:

Een stackalloc_expression is onveilig als alle volgende beweringen waar zijn:

  • De stackalloc_expression wordt geconverteerd naar een Span<T> of een ReadOnlySpan<T>.
  • De stackalloc_expression heeft geen stackalloc_initializer.
  • De stackalloc_expression wordt gebruikt binnen een lid dat is SkipLocalsInitAttribute toegepast.

In deze contexten kan de resulterende stackruimte onbekende geheugeninhoud hebben en wordt deze geconverteerd naar een type dat een veilige wrapper biedt rond onbeheerde geheugentoegang. Dit schendt het contract van Span<T> en ReadOnlySpan<T>, en moet dus worden onderworpen aan extra controle door de auteur en revisoren van dergelijke code.

In tegenstelling tot andere wijzigingen in unsafe regels die ontspanning zijn, is dit een verstrenging, en daarom geldt dit alleen onder opt-in voor de bijgewerkte regels voor geheugenveiligheid om een onderbreking te voorkomen.

Note

Dit betekent dat het toewijzen van een stackalloc aanwijzer altijd veilig is, ongeacht de context.

Houd er rekening mee dat een stackalloc van een beheerd type een fout blijft.

sizeof

Voor bepaalde vooraf gedefinieerde typen sizeof is altijd constant en veilig geweest (§12.8.19) en dat blijft ongewijzigd. Voor andere typen, sizeof gebruikt om onveilige context (§24.6.9) te vereisen, maar is nu veilig, ongeacht of u zich aanmeldt voor de bijgewerkte regels voor geheugenveiligheid.

Overschrijven, overnemen en implementeren

Het is een geheugenveiligheidsfout die moet worden toegevoegd unsafe op lidniveau bij elke overschrijving of implementatie van een lid dat oorspronkelijk niet onveilig is, omdat bellers mogelijk de basisdefinitie gebruiken en geen toevoeging van unsafe een afgeleide implementatie zien.

Gemachtigden en lambdas

Het is een geheugenveiligheidsfout om een vereist onveilig lid te converteren naar een gemachtigde type buiten de unsafe context. Delegeringstypen en functietypen kunnen niet onveilig zijn. Het is een compilatiefout die moet worden toegepast unsafe op een lambda-symbool.

extern

Omdat extern methoden worden gebruikt voor systeemeigen locaties die niet kunnen worden gegarandeerd door de runtime, kan de compiler niet zien of ze veilig of onveilig zijn. Zelfs methoden die alleen parameters op waarde nemen unmanaged , kunnen niet veilig worden aangeroepen door C#, omdat de aanroepconventie die voor de methode wordt gebruikt, onjuist kan worden opgegeven door de gebruiker en handmatig moet worden geverifieerd door controle.

Daarom moet volgens de bijgewerkte regels voor geheugenveiligheid elke extern methode expliciet worden gemarkeerd als unsafe of safe.

extern methoden van assembly's die gebruikmaken van de verouderde regels voor geheugenveiligheid worden niet impliciet unsafe beschouwd omdat extern ze worden beschouwd als implementatiedetails die geen deel uitmaken van het openbare oppervlak. extern wordt niet gegarandeerd bewaard in referentieassembly's.

Houd er rekening mee dat dit verschilt van de compatibiliteitsmodus die ook van toepassing is op assembly's met verouderde regels, omdat methoden met aanwijzers in handtekening altijd een onveilige context nodig hebben op de oproepsite.

Onveilige modifiers en contexten

Zoals vandaag de dag wordt behandeld door de onveilige contextspecificatie, unsafe gedraagt zich op lexicale wijze, waarbij de volledige teksttekst van het unsafe blok als context unsafe wordt gemarkeerd (met uitzondering van iterator-lichamen) en ook enkele omringende contexten in het geval van declaraties:

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
}

Als u zich aanmeldt voor de bijgewerkte regels voor geheugenveiligheid, unsafe markeert een lid deze als onveilig, breidt u de controleverplichting uit naar de beller en introduceert unsafe u geen context (in plaats daarvan worden alleen expliciete unsafe regio's in de hoofdtekst contexten vastgesteldunsafe).

unsafe in de volgende declaraties wordt een fout gegenereerd omdat deze geen betekenis meer heeft:

  • delegate,
  • statische constructor,
  • destructor,
  • typedeclaratie (class, structenz.).

unsafe in een constructor wordt een unsafe context binnen de initialisatiefunctie geïntroduceerd, dat wil bijvoorbeeld dat een unsafe constructor een vereist onveiligebase of this constructor aanroept.

Voor typen zonder parameters zijn onveilige constructors vereist die niet voldoen aan de new() beperking. Daarnaast voldoen structs met parameterloze constructors niet aan de struct beperking.

unsafe op een lid wordt niet toegepast op geneste anonieme of lokale functies binnen het lid. Hetzelfde geldt voor anonieme en lokale functies die binnen een unsafe blok zijn gedeclareerd (ze bevinden zich nog steeds in een unsafe context zoals altijd, maar ze worden niet onveilig). Als u een lokale functie als onveilig wilt markeren, moet deze handmatig worden gemarkeerd als unsafe. Lambdas kan niet worden gemarkeerd als onveilig ( het unsafe trefwoord is niet toegestaan).

Wanneer een lid is partial, moeten beide delen het vandaag over de unsafe wijzigingsfunctie eens zijn, ongewijzigd van C#-regels.

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
}

Voor eigenschappen get en set/init accessors kunnen onafhankelijk worden gedeclareerd als unsafe; het markeren van de hele eigenschap als unsafe betekent dat zowel de getset/init als de toegangsrechten onveilig zijn. Het is momenteel niet mogelijk om wijzigingen aan te brengen in gebeurtenistoegangsors en dit voorstel verandert niet dat, dat wil gezegd, add en remove gebeurtenistoegangsors niet onafhankelijk kunnen worden gedeclareerd als unsafe. Alleen als de hele gebeurtenis is gemarkeerd als unsafe, betekent dit dat de accessors onveilig zijn; anders zijn ze veilig.

Velden

unsafe in een veld wordt deze ook gemarkeerd als onveilig en introduceert unsafe geen context in de initialisatiefunctie. De compatibiliteitsmodus is ook van toepassing op velden.

Als u een eigenschap of gebeurtenis markeert, omdat unsafe het back-upveld niet onveilig is.

In een type met [StructLayout(LayoutKind.Explicit)] of [ExtendedLayout]moeten alle exemplaarvelden worden gemarkeerd safe of unsafe. Als het veld 'verborgen' is achter een automatische eigenschap of een veldachtige gebeurtenis, wordt de safe/unsafe vereiste verplaatst naar de gebeurtenis voor automatische eigenschap of veldachtige gebeurtenis.

Metagegevens

Wanneer een assembly wordt gecompileerd met de nieuwe regels voor geheugenveiligheid, wordt deze gemarkeerd met MemorySafetyRulesAttribute (hieronder beschreven), ingevuld 15 als de taalversie. Dit is een signaal voor downstreamgebruikers dat alle leden die in de assembly zijn gedefinieerd, correct worden toegeschreven RequiresUnsafeAttribute aan (hieronder beschreven) als er een unsafe context nodig is om ze aan te roepen. Elk lid in een dergelijke assembly die niet is gemarkeerd RequiresUnsafeAttribute , hoeft unsafe geen context te worden aangeroepen, ongeacht de typen in de handtekening van het lid.

Het is een fout om het of RequiresUnsafeAttribute een symbool expliciet in de MemorySafetyRulesAttribute bron toe te passen.

De compiler negeert RequiresUnsafeAttribute-gemarkeerde leden van assembly's die gebruikmaken van de verouderde regels voor geheugenveiligheid (in plaats daarvan wordt de compatibiliteitsmodus daar gebruikt).

Wanneer een niet-type lid is gemarkeerd als unsafe, zal de compiler een RequiresUnsafeAttribute toepassing op het lid in metagegevens synthetiseren. Wanneer voor een gebruiker onveilig lid is vereist, worden verborgen leden gegenereerd, zoals de get/set-methoden van een eigenschap voor automatisch instellen, zowel het gebruikersgerichte lid als alle verborgen leden die door dat gebruikergerichte lid zijn gegenereerd, zijn onveilig enRequiresUnsafeAttribute worden ze op alle leden toegepast.

De MemorySafetyRulesAttribute en RequiresUnsafeAttribute definitie wordt indien nodig gesynthetiseerd door de compiler volgens standaard bekende lidregels.

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

Compatibiliteitsmodus

Voor compatibiliteitsdoeleinden en om het aantal fout-negatieven te verminderen dat optreedt bij het inschakelen van de nieuwe regels, hebben we een terugvalregel voor modules die niet zijn bijgewerkt naar de nieuwe regels. Voor dergelijke modules wordt een lid als onveilig beschouwd als het een aanwijzer of functiepointertype bevat ergens tussen de parametertypen of het retourtype (kan worden genest in een niet-aanwijzertype, bijvoorbeeld int*[]). Houd er rekening mee dat dit niet van toepassing is op aanwijzers in beperkingstypen (bijvoorbeeld where T : I<int*[]>) omdat deze eerder geen onveilige context nodig zouden hebben op de oproepsites.

Dit omvat geen vervangen algemene parameters (bijvoorbeeld de methode I<T>.M(T) wanneer deze wordt vervangen T door int*[]) omdat er geen typeveilige manier is voor het doellid om dat aanwijzertype toch te gebruiken.

Voor dergelijke compatibiliteitsmodus moeten onveilige leden een unsafe context gebruiken, zelfs van bellers die niet hebben gekozen voor de bijgewerkte regels voor geheugenveiligheid. Dit moet een 'dip' voorkomen waarbij langVersion alleen wordt bijgewerkt (maar niet de versie van regels voor geheugenveiligheid bijwerkt) de meeste aanwijzerbewerkingen veilig maakt (inclusief aanroepende functies met aanwijzers in handtekening die waarschijnlijk worden gemarkeerd als onveilig wanneer ze zijn aangemeld bij de bijgewerkte regels) en waardoor code minder beveiligd wordt in dit migratievenster.

VB

We hoeven geen ondersteuning toe te voegen aan Visual Basic voor onveilige leden, omdat er momenteel geen unsafe contexten in VB zijn en ook geen manier om met aanwijzers te werken.

unsafe Expressies

Een unsafe expressie introduceert een minimale unsafe context voor het evalueren van één expressie. Het is handig in situaties waarin een unsafe blok het unsafe bereik onnodig zou verbreed of waarbij unsafe blokken niet syntactisch kunnen worden gebruikt (zoals in catch filters, veld initializers, constructor-initializers en de operandpositie van await).

Syntax

Er unsafe_expression wordt een toegevoegd als een primary_no_array_creation_expression:

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

Het is onderhevig aan dezelfde AllowUnsafeBlocks vereiste als het unsafe trefwoord elders.

Semantiek

Hiermee unsafe_expression wordt een unsafe context voor het evalueren van de expressie vastgelegd. Dit betekent dat aanwijzerdeducties, functieaanwijzeraanroepen en aanroepen naar vereist onveilige leden allemaal zijn toegestaan binnen de ingesloten expressie. Het type en de waarde van de unsafe_expression expressie zijn het type en de waarde van de ingesloten expressie.

De unsafe context die door een an unsafe_expression is ingesteld, loopt niet verder dan het haakje sluiten.

Voorbeelden van motivatie en migratie

Verschillende syntactische posities geven helemaal geen blokken toe unsafe , maar kunnen subexpressies bevatten die aanroepen onveilige leden vereisen . Zonder unsafe expressies moet voor het migreren van deze code de onveilige subexpressie worden geëxtraheerd in een lokale helperfunctie of een tijdelijke variabele, waardoor de intentie wordt verborgen en de uitgebreidheid toeneemt.

await op een onveilige methode vereist . Een await expressie kan niet in een unsafe blok worden weergegeven. Wanneer de te wachten methode onveilig wordt, is een tijdelijke variabele vereist om de taak te bewaren voordat deze kan worden gewacht:

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

Catch-filters. Het when filter van een catch component is een expressie, geen hoofdtekst van de instructie. Een unsafe blok kan alleen instructies omringen, dus er is geen plek om er een rond de filterexpressie te plaatsen. Als de try hoofdtekst een await expressie bevat, kan een unsafe blok ook de hele try/catch instructie niet omsluiten.await Dit is ook niet toegestaan binnen een unsafe blok. Wanneer een methode die in een filter wordt gebruikt , onveilig wordt, is het enige alternatief zonder unsafe expressies een helper:

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

Initialisatiefunctie voor velden. Onder de bijgewerkte regels unsafe introduceert unsafe een veld geen context in de initialisatiefunctie. Wanneer de initialisatiefunctie van een veld een vereist onveilig lid aanroept, biedt een unsafe expressie de context zonder dat hiervoor een helpermethode is vereist:

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

Initialisatieprogramma's voor constructors. this(...) en base(...) lijsten met initialisatieargumenten zijn expressies, geen instructieteksten. Als een van deze argumenten een onveilig lid aanroept , is er opnieuw geen plaats om een unsafe blok in te voegen, zodat de aanroep anders naar een helper moet worden verplaatst:

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

Inline-aanroepen. Over het algemeen houdt het verpakken van alleen de onveilige subexpressie het controlebereik strak en vermijdt het ophalen van omringende veilige code (zoals argumentevaluatie of een insluitmethodeoproep) in de unsafe context:

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

Vragen

(beantwoord) Gebruiken RequiresUnsafeAttribute om aan te geven dat onveilige leden vereist zijn

In plaats van het unsafe trefwoord op het lid te gebruiken om aan te geven dat onveilige leden zijn vereist , kunnen we een kenmerk () gebruiken datRequiresUnsafeAttribute is toegepast op het lid (en niet de betekenis van de unsafe wijzigingsfunctie voor leden wijzigen).

Voordelen van unsafe:

  • vergelijkbaar met andere talen en dus gemakkelijker te begrijpen,
  • beter detecteerbaar dan een kenmerk.

Voordelen van een kenmerk (of een ander trefwoord):

  • vermijdt het verbreken van bestaande leden die zijn gemarkeerd als unsafe,
  • incrementele acceptatie mogelijk (lid per lid),
  • dwingt niet af dat het hele lichaam wordt gemarkeerd als unsafe (zelfs met unsafe trefwoord dat we kunnen veranderenunsafe om geen effect op lichamen te hebben),
  • staat toe dat alle onveilige fouten worden onderdrukt zonder dat u het lid zelf hoeft te markeren als vereist onveilig (voorbeelden).

Discussies:

Antwoord: gebruik trefwoord unsafe om aan te geven dat onveilige leden zijn vereist .

Lokale functies/lambda veilige contexten

Op dit moment unsafe is een methodetekst lexisch bereik. Geneste lokale functies of lambdas nemen dit over en hun lichamen bevinden zich in een onveilige context voor geheugen. Is dit gedrag dat we in de taal willen houden? Houd er rekening mee dat als de unsafe wijzigingsfunctie die wordt gebruikt om aan te geven dat de aanroeper onveilig moet zijn, dit gevolgen kan hebben voor de handtekening van de methode. Zoals momenteel voorgesteld, behouden geneste anonieme en lokale functies de onveilige context van hun lid niet.

unsafeType gemachtigde

We kunnen het markeren van gedelegeerdentypen en lambdas (en functietypen) toestaan, omdat dit onveilig is. Hiervoor zijn verschillende aanvullende regels vereist (buiten unsafe context):

  • gebruik van niet-onveilige gemachtigden als typeargumenten is niet toe te laten.
  • het converteren van deze gemachtigden niet naar iets dat niet onveilig is (DelegateenExpressionobject ), Zonder dit is er een risico op het afdwingen unsafe van aantekeningen op de verkeerde plek en een gebied waar het echte gebied van unsafety niet correct wordt uitgelicht.

Lambda/methode groepsconversie naar veilige gedelegeerdentypen

Als we lambdas en gemachtigden toestaan unsafe , moet de conversie van een groep met onveilige lambda's of methoden naar een niet-vereist onveilig gemachtigdentype zonder waarschuwing of fout in een unsafe context worden toegestaan? Als we dit niet doen, kan het behoorlijk pijnlijk zijn voor verschillende onderdelen van het ecosysteem, met name alle opsommingspunten die worden doorgegeven via LINQ-query's.

Lambda/methode groep natuurlijke typen

Tegenwoordig is de enige echte impact op semantiek en codegen (naast aanvullende metagegevens) het wijzigen van de function_type van een lambda- of methodegroep wanneer unsafe deze zich in de handtekening bevindt. Als we dit zouden vermijden, zou er ook geen echte impact zijn op, waardoor gebruikers meer vertrouwen kunnen hebben dat gedrag niet subtiel onder de motorkap is veranderd.

Note

Als we besluiten om de mogelijkheid te behouden om lambdas te hebben unsafe , moeten we dit voorstel bijwerken om een syntaxiswijziging op te nemen zodat lambdas in de eerste plaats kunnen worden gedeclareerd unsafe .

Aanwijzers naar beheerde typen

Met C# 11 kunnen aanwijzers beheerde typen met een waarschuwing beheren. Moeten we die waarschuwing voor adres-of-bewerkingen versoepelen? We denken dat het probleem alleen is wanneer de gebruiker een dergelijke aanwijzer afwijst, die onder normale onveilige evolutieregels valt. Maar hoe zit het met sizeof?

stackalloc zoals geïnitialiseerd

Tegenwoordig beschouwt stackallocde specificatie altijd geheugen als niet-geïnitialiseerd en wordt aangegeven dat de inhoud niet is gedefinieerd, tenzij de inhoud handmatig is gewist of toegewezen. Beschouwen we dit als een specificatiefout of moeten we wijzigen wat we voor stackalloc doeleinden overwegenunsafe?

stackalloc Regel

LDM moet de stackalloc hierboven gedefinieerde regel bevestigen en of deze van toepassing moet zijn, ongeacht opt-in, zoals andere aanwijzers gerelateerde wijzigingen.

AllowUnsafeBlocks

AllowUnsafeBlocks De betekenis van is momenteel ongewijzigd- het moet worden ingesteld true op om het unsafe trefwoord te kunnen gebruiken of SkipLocalsInitAttribute. Moeten we dit SkipLocalsInitAttribute niet vereisen onder de bijgewerkte regels, omdat BCL dat kenmerk kan markeren als vereist onveilig? Moeten we dit ook voor het safe trefwoord vereisen? Moeten we dit eisen voor zowel unsafe blokken als unsafe liddeclaraties of een andere combinatie hiervan?

(beantwoord) unsafe expressies

Andere talen met uitgebreidere unsafe functies zijn toegevoegd unsafe als een expressie die de ergonomische gebruikers verbetert en auteurs in staat stelt om nauwkeuriger te beperken waar unsafe wordt gebruikt. Is dit iets wat we willen hebben in C#? Overweeg een inline-aanroep naar een unsafe lid dat de veiligheid rechtstreeks afhandelt: op dit moment moet de auteur de volledige instructie in een unsafe blok verpakken, het bereik van de unsafe context uitbreiden of de binnenste functieaanroep uitsplitsen in een tussenliggende variabele.

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

Antwoord: ja. Zie de gedetailleerde ontwerpsectie.

Meer unsafe contexten en ontspanning

Moeten we meer beperkingen rond unsafe en aanwijzerparameters in iterators en asynchrone methoden versoepelen? Vooral het toestaan await UnsafeMethod() zou nuttig zijn omdat gebruikers dat nu moeten herschrijven.Task t; unsafe { t = UnsafeMethod(); } await t; Zie ref/onveilig in iterators/asynchroon voor meer informatie.

Moeten we ook in veilige context toestaan &UnsafeMethod ? Zoals het voorstel al aangeeft, is hiervoor context vereist unsafe als de methode wordt gemarkeerd als unsafe. Maar omdat we alleen het adres krijgen, dat context nodig heeft unsafe wanneer de deductie/aangeroepen wordt, kunnen we het adres van zichzelf in een veilige context toestaan.

Onveilige ontspanningen gated op LangVersion

Moeten we de onveilige contextontruimingen onvoorwaardelijke versoepelen op LangVersion?

  • LDM 2026-04-06: ze zijn niet afhankelijk van de versie van de geheugenveiligheidsregels
  • TODO: Hoe zit het met LangVersion?

(beantwoord) unsafe op typen

We kunnen overwegen om niet automatisch het hele lexicale bereik van een unsafe type te maken als context unsafe en te waarschuwen voor een unsafe type, omdat dit geen betekenis zou hebben.

  • LDM 2025-11-12: unsafe voor een type heeft geen betekenis
  • LDM 2026-05-13: dit is een fout (kan opnieuw worden bekeken op basis van feedback)

Antwoord: unsafe bij een type is een fout (kan opnieuw worden bekeken op basis van feedback) onder de bijgewerkte regels.

unsafe op accessors

We staan unsafe onlangs eigenschapstoegangsors toe, maar niet op gebeurtenistoegangsors, in overeenstemming met andere vooraf bestaande modifiers. Dit betekent ook dat unsafe op een eigenschap slechts een snelkoppeling is voor unsafe de accessors. Maar tegelijkertijd partialmoeten de unsafe modifiers overeenkomen met:

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

Misschien moet het zich op dezelfde manier readonlygedragen, dat wil bijvoorbeeld, niet op unsafe zowel de eigenschap als de bijbehorende toegangsfunctie tegelijk:

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

(beantwoord) Onderdrukken van onveilige fouten in edge-casescenario's toestaan

Hoe moeten we onderdrukken van onveilige fouten in de volgende scenario's toestaan?

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

Om de onveilige fouten te onderdrukken, moeten we op een of andere manier een unsafe context introduceren in de handtekening van die leden. Maar het unsafe trefwoord introduceert unsafe geen context meer. We kunnen een unsafe context in de handtekening introducerenunsafe, maar het afdwingen van het maken van een constructor vereist onveilig wanneer het aanroepen van een basis onveilige constructor jammer is. We kunnen vereisen onveilig gebruik maken in handtekeningenwaarschuwingen die gebruikers ter plaatse kunnen onderdrukken als ze deze zeldzame edge-gevallen raken.

Er is een vergelijkbaar probleem met typen, omdat unsafe op deze typen geen context wordt geïntroduceerd unsafe :

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:
    • niet-uitvoerbare code, zoals kenmerktoepassing, moet een niet-onderdrukbare fout blijven (totdat we wat feedback horen)
    • andere edge-zaken zoals new() kunnen ook een fout blijven totdat we feedback horen
    • niet weigeren om parameterloze constructors zonder parameters te declareren
    • alleen de initialisatiefunctie van een unsafe constructor kan een vereist onveiligebase of this constructor aanroepen

verzamelingen params

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

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

Deze declaratie kan eenvoudig worden toegestaan omdat het aanroepen van een dergelijke methode toch een unsafe context vereist omdat de onveilige params verzameling is samengesteld op de oproepsite. Hoewel dat de verklaring effectief onveilig maakt, kunnen we alleen de unsafe aantekening vereisen of tenminste waarschuwen voor dat feit. Houd er rekening mee dat de params verzamelingscase vandaag een fout is die overeenkomt met hoe andere vergelijkbare functies zich hier gedragen (Obsolete, UnmanagedCallersOnly), maar dat kan een implementatiefout zijn.

Bekende leden

Om de implementatie eenvoudig en gezond te maken, stellen we voor dat de compiler vrij is om ervan uit te gaan dat alle bekende leden (zoals Array.Length) kunnen worden aangenomen dat ze veilig zijn (dat wil gezegd niet onveilig zijn).

Xml-documenten

Het doorgeven van een verplichting aan bellers krijgt een verantwoordelijkheid om duidelijk te maken wat die verplichting is. Moeten we dit verder formaliseren dan wat al kan worden weergegeven in XML-documenten?

Leden die zijn gemarkeerd unsafe , moeten opmerkingen hebben die aangeven wat nodig is voor de aanroeper om ervoor te zorgen dat de code juist is. Om het gemakkelijker te zien en gemakkelijker te onderscheiden in documentatie, zou een nieuwe XML-documenttag nuttig zijn: <safety /> Het zou worden verwacht dat alle voorwaarden vóór/na de voorwaarden in <safety> blok zouden worden geplaatst.

Als u redenering van elk unsafe blok wilt documenteren, raden we het gebruik van // SAFETY opmerkingen aan, vergelijkbaar met Rust. Moeten we deze ook controleren door de compiler (bijvoorbeeld een off-by-default waarschuwing hebben) of die laten staan aan een analyse?

Meer betekenisloze unsafe waarschuwingen

Moeten meer declaraties de betekenisloze unsafe waarschuwing of fout opleveren? Bijvoorbeeld methoden met lege lichamen (of extern), enzovoort. We hebben al een IDE-analyse voor onnodig unsafe .

Moet [ModuleInitializer] unsafe void M() { } een fout zijn, vergelijkbaar met een statische constructor?

(beantwoord) unsafe Velden

Vandaag wordt er geen voorstel unsafe gedaan op een veld. Het kan echter nodig zijn om deze toe te voegen, zodat elke leesbewerking van of schrijfbewerking naar een veld dat is gemarkeerd als unsafe in een unsafe context moet zijn. Dit zou ons in staat stellen om beter aantekeningen te maken bij de zorgen rond code, zoals:

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

Moeten we ook het backingveld van de eigenschap markeren als unsafe?

Om consistent te zijn met onze beslissing voor leden, zou het goed zijn om unsafe op een veld ook geen context te introduceren unsafe . Als onveilige bewerkingen vereist zijn in de initialisatiefunctie van het veld, kan de gebruiker deze altijd inkapselen in een methode of kunnen we expressies introducerenunsafe.

  • LDM 2026-05-13: velden kunnen worden gemarkeerd als onveilig via unsafe; de initialisatieprogramma's zijn niet in unsafe context.

(beantwoord) Expliciete indeling

Moeten velden in de structs zijn gemarkeerd als [StructLayout(Explicit)] of [ExtendedLayout] moeten worden gemarkeerd als unsafe?

Aanbeveling: ja.

Expliciete indelings- en back-upvelden

Als compiler een backingveld voor een automatische eigenschap of veldachtige gebeurtenis in een Explicit/Extended type samengeeft, moeten we safe/unsafe in plaats daarvan de eigenschap/gebeurtenis synthetiseren? Anders wordt de gebruiker gedwongen om deze automatische declaraties uit te breiden naar handmatig veld plus declaraties van wrapperleden. Hoe zit het met een primaire constructorparameter waarmee een back-upveld wordt opgehaald? Zowel safe als unsafe modifier is momenteel niet toegestaan voor een parameterdeclaratie.

[Out] en [SkipLocalsInit]

Omdat vb bijvoorbeeld niet garandeert dat [Out] parameters worden geïnitialiseerd, kan het [SkipLocalsInit]aanroepen van dergelijke parameters in C# worden overwogen unsafe . Aan de andere kant voelt het alsof het probleem van de aanwijzer niet naleeft [Out] dat het contract niet naleeft (het kan op veel andere manieren onveilig zijn).

Als we besluiten dat deze gevallen moeten zijn unsafe, kunnen we methoden uitsluiten die we weten te hebben gekozen (die zijn momenteel afkomstig van C# die het juiste gebruik [Out] garandeert, maar als andere talen de nieuwe regels implementeren, moeten ze dat ook garanderen).

Het adres van een niet-geïnitialiseerde variabele nemen

Vandaag de dag kan het nemen van het adres van een niet zeker toegewezen variabele overwegen dat variabele definitief is toegewezen, waardoor niet-geïnitialiseerd lid wordt weergegeven. We hebben een aantal opties om dat op te lossen:

  1. Vereisen dat variabelen zeker worden toegewezen voordat een adres-of-operator erop kan worden gebruikt.
  2. Maak het nemen van het adres van een niet-geïnitialiseerde variabele onveilig.

Voorbeelden:

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

Waarde van MemorySafetyRulesAttribute

Wat moet de versie van de "enabled"/"updated" geheugenveiligheidsregels zijn? 2? 15? 11? Zie ook SDK Onveilige acceptatie en meer geleidelijke opt-in?.

(beantwoord) Meer geleidelijke opt-in?

Vandaag de dag krijgt u twee dingen tegelijk: het afdwingen van de onveilige regels in uw eigen code en een signaal dat wordt gepubliceerd aan uw consumenten (via een kenmerk op assemblyniveau) dat uw aantekeningen opzettelijk zijn. Er kunnen gebruikers zijn die de afdwingingsdiagnose willen krijgen terwijl ze aantekeningen maken, zonder dat ze klaar zijn om te publiceren dat ze hun assembly volledig hebben geannoteerd. Moeten we een 'middelste' opt-in-niveau hebben waarmee de onveilige diagnostische gegevens worden weergegeven als waarschuwingen, en de volledige opt-in zou hen promoveren tot fouten?

(beantwoord) Fijnmazige opt-in

Bieden een fijnmazig, regiogebaseerd opt-in-mechanisme dat vergelijkbaar is met het mechanisme dat we hebben gebouwd voor null-referentietypen, waar gebruikers instructies kunnen gebruiken om de functie in te schakelen voor specifieke regio's van broncode? Zie ook Opt in/out voor coderegio's.

(beantwoord) extern impliciet onveilig

Dit is momenteel de enige plaats waar RequiresUnsafeAttribute wordt gesynthetiseerd zonder een expliciet unsafe trefwoord. Zijn we in orde met deze uitschieter?

CoreLib maakt tegenwoordig ook veel externe methoden (FCalls) beschikbaar als veilig. Voor het behandelen van externe methoden als impliciet onveilig moet u de impliciet onveilige externe methoden verpakken met een veilige wrapper. We kunnen situaties tegenkomen waarbij het toevoegen van de extra wrapper moeilijk is vanwege runtime-implementatiedetails.

  • LDM 2026-04-01: extern leden moeten expliciet worden gemarkeerd als veilig of onveilig
  • LDM 2026-04-06: dezelfde beslissing herhaald
  • LDM 2026-04-13: tijdelijke beslissing om trefwoord te gebruiken safe
  • LDM 2026-05-13: gebruik safe trefwoord (nog steeds geopend om opnieuw te bezoeken)

Antwoord: extern leden moeten worden gemarkeerd unsafe of safe (we voegen er een nieuw trefwoord voor toe, maar kunnen dat opnieuw bekijken voordat de functie wordt verzonden, op basis van feedback).

(beantwoord) unsafe standaardinstellingen voor context in leden

We kunnen overwegen om niet automatisch de hele hoofdtekst van een unsafe methode een unsafe context te maken. Rust deed dit in RFC 2585, met de motivatie dat het helpt het bereik van unsafe blokken te verminderen tot de locaties waar unsafe daadwerkelijk wordt gebruikt. We kunnen hetzelfde doen in C#, als een waarschuwing of een fout, met vergelijkbare motivaties.

  • LDM 2026-04-22: Ja, unsafe in handtekening maakt de hoofdtekst niet unsafe

(beantwoord) new() Beperking

Willen we ondersteuning bieden new() voor onveilige functies (iets dat momenteel niet wordt ondersteund in de compiler voor andere functies, zoals 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: typen met vereist onveilige parameterloze constructors mogen niet voldoen aan de new() beperking

new() beperking en usings

Hoe moet het zich gedragen in aliassen en statisch gebruik?

  • Als het een fout is bij de using declaratie, onderdrukt via het unsafe trefwoord dat we daar al ondersteunen, of
  • zou het normaal gesproken een fout moeten zijn op de gebruikssite, zoals deze zou worden gebruikt als deze rechtstreeks zonder alias of statisch zou worden gebruikt?

Note

In het tweede geval moeten we 'betekenisloze unsafe' waarschuwing toevoegen voor het gebruik van aliassen en statische gebruiksfuncties.

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

Houd er rekening mee dat andere beperkingen zich vandaag gedragen als de vorige optie:

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

Moeten er meer constructies zijn unsafe?

  • dynamic (moet waarschijnlijk overeenkomen met wat BCL besluit voor reflectie-API's)

(beantwoord) Hoe fouten willen we scheeftrekken?

Vraagtekst

Het eerste voorstel is een maximaal brekende benadering, voornamelijk als een litmustest voor hoe agressief we willen zijn. Het stelt voor om geen mogelijkheid om secties van de code in/uit te schakelen, de betekenis van unsafe methoden te wijzigen, het gebruik van unsafe typen te verbieden, fouten te gebruiken in plaats van waarschuwingen, en over het algemeen dwingt de migratie allemaal tegelijk uit te voeren, op het moment dat de compiler wordt bijgewerkt (en vervolgens mogelijk herhaaldelijk als afhankelijkheden worden bijgewerkt en toegevoegd unsafe aan leden die al in gebruik waren). We hebben echter een schat aan ervaring bij het aanbrengen van wijzigingen zoals deze, die we kunnen benutten om de omvang van de uitsplitsing te bepalen en incrementele acceptatie mogelijk te maken. Deze opties worden hieronder behandeld.

Afmelden/afmelden voor coderegio's

Dit is niet de eerste keer dat C# het 'basis'-geval van niet-geannoteerde code opnieuw heeft gedefinieerd. C# 8.0 heeft de functie voor null-verwijzingstypen geïntroduceerd, die op veel manieren kan worden gezien als blauwdruk voor de vormgeving van de unsafe functie. Het had vergelijkbare doelen (voorkom bugs die miljarden dollars kosten door de manier waarop standaard C# wordt geïnterpreteerd opnieuw te definiëren) en een vergelijkbare algemene functieset (voeg nieuwe informatie toe aan typen om statussen door te geven en fouten te voorkomen). Het was ook sterk brekend en had een sterke set opt-in- en opt-outfunctionaliteit nodig om de functie in de loop van de tijd door codebases te kunnen gebruiken. Deze functionaliteit is de context 'nullable reference type'. Dit is een lexical bereik dat de compiler informeert over een bepaalde regio in code, zowel hoe niet-geannoteerde typeverwijzingen worden geïnterpreteerd als welke typen waarschuwingen aan de gebruiker moeten worden gegeven. We kunnen dit ook gebruiken als model unsafe voor het toevoegen van een 'context van veiligheidsregels' of vergelijkbaar om te bepalen of deze nieuwe regels al dan niet worden toegepast.

Een voordeel dat we hebben met de nieuwe unsafe functies is dat ze veel minder voorkomen. Hoewel er een behoorlijk aantal unsafe aanroepen in topbibliotheken is, zijn onze schattingen voor het percentage topbibliotheken dat wordt gebruikt unsafe veel lager dan 'elke regel C#-code die ooit is geschreven'. Hopelijk betekent dit dat, terwijl sommige mogelijkheid om zich voor te kiezen/uit te schakelen mogelijk nodig is, we niet zo ingewikkeld een mechanisme als nullable hebben, met speciale preprocessorswitches en dergelijke.

Waarschuwingen versus fouten

Het voorstel geeft momenteel aan dat de veiligheidsvereisten voor geheugen momenteel worden afgedwongen via een waarschuwing, in plaats van fout. Dit is gebaseerd op onze ervaring met het werken met de null-functie, waarbij waarschuwingen toegestaan zijn dat codebasissen incrementeel de nieuwe functie gebruiken en niet hoeven om grote stukjes code in één keer te converteren. We verwachten dat een vergelijkbaar proces nodig is voor onveilige waarschuwingen: veel codebasissen kunnen de nieuwe regels wereldwijd inschakelen en doorgaan met hun leven. Maar we verwachten dat de codebasissen die we het belangrijkst vinden bij het aannemen van de nieuwe regels grote hoeveelheden code hebben om aantekeningen te maken en we willen dat ze verder kunnen gaan met de functie, in plaats van een muur met fouten te zien en onmiddellijk op te geven. Door de vereistenwaarschuwingen te maken, staan we deze codebases toe om waarschuwingen op te lossen, indien nodig, zodat de waarschuwingen overal anders worden uitgeschakeld.

Handtekeningeinden van methode

Op dit moment stellen we voor dat unsafe als trefwoord voor de methode wordt verplaatst van iets dat lexisch is afgestemd zonder een semantische impact op iets dat semantische impact heeft en niet lexisch is. We kunnen dit einde beperken door een nieuw trefwoord te introduceren voor wanneer de aanroeper van een methode of lid zich in een unsafe context moet bevinden, bijvoorbeeld callerunsafe als wijzigingsfunctie.

Standaardwaarden voor brongeneratoren

Voor nullable dwingen we auteurs van generatoren expliciet in te schakelen op null, ongeacht of het hele project standaard heeft gekozen voor de functie, zodat generatoruitvoer niet wordt verbroken door de gebruiker die nullable inschakelt en als fout waarschuwt. Moeten we hetzelfde doen voor brongeneratoren?

Conclusion

Beantwoord in LDM 2025-11-05. Er worden fouten gerapporteerd voor problemen met geheugenveiligheid wanneer de nieuwe regels zijn ingeschakeld en er worden geen uitzonderingen gemaakt voor brongeneratoren.

(beantwoord) Fouten of waarschuwingen?

(beantwoord) Brongenerator betaalbaarheid

(beantwoord) Maker vereisen safe voor leden met unsafe blokken of aanwijzers?

(beantwoord) Compat-modus voor niet-gekozen bellers?

Antwoord: niet-gekozen leden met aanwijzers in handtekening worden beschouwd als onveilig voor bellers die zijn aangemeld.

(beantwoord) De compatibiliteitsmodus uitbreiden?

Moeten we ook rekening houden nint met en System.IntPtr als aanwijzingen? Moeten we rekening houden extern/DllImport met niet-gekozen bellers, omdat dit ook onveilig is ? Moeten we een dekenwaarschuwing hebben wanneer een ingestelde assembly verwijst naar een niet-gekozen assembly?

  • LDM 2026-04-29: geen uitbreidingen (analyses kunnen een aantal minder onveilige signalen dekken)