Evoluzione non sicura

Questione prioritaria: https://github.com/dotnet/csharplang/issues/9704

Sommario

La definizione di unsafe in C# viene aggiornata facendo riferimento ai percorsi in cui vengono usati i tipi di puntatore, in modo che siano posizioni in cui la memoria non gestita dal runtime viene dereferenziata. Queste posizioni sono la posizione in cui si verifica unsafety della memoria e sono responsabili della maggior parte delle cve (vulnerabilità comuni ed esposizioni) classificate come problemi di sicurezza della memoria.

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

Motivazione

Il background per questa funzionalità è disponibile anche in https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.md, che tiene traccia dei cambiamenti più ampi dell'ecosistema che saranno necessari come parte di questa proposta. Questi includono gli aggiornamenti BCL per annotare correttamente i metodi come non sicuri, nonché gli aggiornamenti degli strumenti per una migliore comprensione della posizione in cui si verifica unsafety della memoria. Per C# in particolare, si vuole assicurarsi che la memoria non sicura sia rilevata correttamente dal linguaggio; oggi, può essere difficile esaminare un programma in modo olistico e comprendere tutte le posizioni in cui si verifica l'insicurezza della memoria. Ciò è dovuto al fatto che vari helper come System.Runtime.CompilerServices.Unsafe, System.Runtime.InteropServices.Marshale altri non esprimono che violano la sicurezza della memoria e necessitano di considerazioni speciali. I metodi che quindi usano questi helper non sono immediatamente evidenti e quando si controlla il codice per problemi di sicurezza della memoria (in anticipo durante la revisione o quando si tenta di determinare la causa di una vulnerabilità segnalata), può essere difficile individuare le posizioni che potrebbero contribuire ai problemi.

Storicamente, unsafe in C# si è fatto riferimento a un foro specifico per la sicurezza della memoria: l'esistenza di tipi di puntatore. Il momento in cui un tipo di puntatore non è più coinvolto, C# è perfettamente felice di lasciare che la memoria non sicura giace latente nel codice. Si tratta di questo problema che stiamo cercando di risolvere con questa evoluzione di unsafe in C# e l'ecosistema .NET, etichettando aree in cui la memoria potrebbe verificarsi potenzialmente, rendendo più semplice per i revisori e i revisori comprendere i limiti della potenziale memoria non sicura in un programma. Importante, questo significa che si modificherà il significato di unsafe, non solo aumentandolo. L'esistenza di un puntatore non è di per sé non sicura; l'azione unsafe sta dereferenziando il puntatore. Questo si estende ulteriormente ai tipi stessi; i tipi non possono essere intrinsecamente non sicuri. È solo l'azione di usare un tipo che potrebbe non essere sicuro, non l'esistenza di tale tipo.

Affinché queste informazioni scorrono attraverso il sistema, è quindi necessario avere un modo per contrassegnare i metodi stessi come unsafe. Oggi, unsafe poiché un modificatore di metodo non ha alcun impatto esterno, consente solo l'uso dei puntatori nella firma e nel corpo del membro. In futuro, unsafe poiché un modificatore cambierà pubblicamente il significato del membro; indicherà che il membro ha problemi di sicurezza della memoria e gli eventuali utilizzi devono essere convalidati manualmente dal programmatore utilizzando il membro. Si tratta di un'espansione del significato esistente di unsafe: unsafe in un organismo localizzare l'obbligo di controllo a tale organismo, mentre unsafe su una firma estende tale obbligo al chiamante.

Si tratta di una modifica di rilievo potenzialmente importante per determinati segmenti della base utente C#. La nostra speranza è che, per molti dei nostri utenti, questo è effettivamente trasparente e l'aggiornamento alle nuove regole sarà facile. Tuttavia, dato che alcune superfici API di grandi dimensioni come grandi parti di reflection potrebbero essere contrassegnate unsafe, riteniamo che sia probabile che ci sarà una rampa decente per le nuove regole per evitare completamente biforcando l'ecosistema.

Modifiche radicali

Quando si esegue l'aggiornamento a un compilatore che implementa questa funzionalità del linguaggio, è possibile osservare le modifiche di rilievo seguenti.

  • Se le regole di sicurezza della memoria aggiornate sono abilitate (che potrebbero essere l'opzione predefinita o anche l'unica opzione in una versione futura .NET):
    • unsafe su un membro ora lo contrassegna anche come non sicuro, ovvero i chiamanti devono trovarsi in un unsafe contesto e gli override non possono essere unsafe se il membro di base è sicuro.
    • unsafe in un membro o in un tipo non introduce automaticamente un unsafe contesto, ovvero i blocchi espliciti unsafe devono essere usati per unsafe le operazioni nei corpi dei membri e negli inizializzatori.
    • extern i membri e i campi nel layout esplicito richiedono un esplicito unsafe/safe parola chiave nella dichiarazione.
    • stackalloc in determinate condizioni richiede un unsafe contesto.
    • unsafe il modificatore è un errore nelle dichiarazioni di tipo, nei costruttori statici e nei distruttori, perché non ha alcun effetto.
  • In un nuovo linguaggio:
    • L'inferenza lambda potrebbe considerare più candidati, causando ambiguità di risoluzione dell'overload.
    • safe è ora una parola chiave contestuale che potrebbe interrompere il codice usato come tipo.

Progettazione dettagliata

Terminologia: si chiamano membri non sicuri (precedentemente noti come chiamante-unsafe) se

Sintassi

Questa proposta introduce:

Questa nuova sintassi è disponibile in new LangVersion, ma indipendentemente dal consenso esplicito , in base alla premessa che stiamo cercando di renderla in modo che qualsiasi cosa sia necessario fare quando si è acconsenti esplicitamente, è possibile farlo prima di acconsentire esplicitamente.

Regole esistenti unsafe

La specifica C# esistente include una sezione di grandi dimensioni dedicata al unsafecodice :24 Unsafe. Viene definito come normativo in modo condizionale, perché non è necessario per un compilatore C# valido per supportare la unsafe funzionalità. Gran parte di ciò che è attualmente considerato normativamente condizionale non sarà più così dopo questa modifica, poiché la maggior parte della definizione dei puntatori non è più considerata non sicura in se stessa. Tipi di puntatore, variabili fisse e spostabili, tutte le espressioni puntatore (ad eccezione dell'accesso indiretto del puntatore, dell'accesso ai membri del puntatore e dell'elemento puntatore) e l'istruzione fixed non sono più considerate unsafeed esistono in C# normali senza che sia necessario usare in un unsafe contesto. Analogamente, anche la dichiarazione di un buffer a dimensione fissa o di un inizializzato stackalloc è perfettamente valida in C#. Per tutti questi casi, si accede solo alla memoria non sicura.

Importante, questi relax puntatore si applicano indipendentemente dal fatto che un assembly acconsenta alle regole di sicurezza della memoria aggiornate. Solo le operazioni che effettivamente dereferenziano o altrimenti accedono direttamente alla memoria a punta continuano a richiedere un unsafe contesto. Ciò consente un percorso di migrazione incrementale: gli utenti possono modificare il codice esistente spostandosi unsafe verso l'interno quando il membro gestisce il rischio internamente o verso l'esterno quando il chiamante deve partecipare al controllo, prima di capovolgere l'opzione di consenso esplicito a livello di assembly.

Data l'ampia riscrittura sia della unsafe sezione del codice che delle altre parti della specifica C# intrinseca in questa modifica, sarebbe poco complesso e probabilmente non sarebbe utile fornire una diff line-by-line delle regole esistenti della specifica. Verrà invece fornita una panoramica della modifica da apportare in una determinata sezione, nonché nuove regole specifiche per ciò che è consentito nei unsafe contesti.

Ridefinizione di espressioni che richiedono contesti non sicuri

Le espressioni seguenti richiedono un unsafe contesto quando viene usato:

Oltre a queste espressioni, le espressioni e le istruzioni possono anche richiedere in modo condizionale un unsafe contesto se dipendono da qualsiasi simbolo contrassegnato come unsafe. Ad esempio, la chiamata di un metodo che richiede un'operazione non sicura causerà la richiesta del invocation_expression di un unsafe contesto. Le istruzioni con chiamate incorporate (ad esempio usings, foreache simili) possono richiedere anche un unsafe contesto quando usano un membro non sicuro richiesto .

Quando si dice "richiede un contesto non sicuro" o simile in questo documento, significa generare un errore che il costrutto richiede l'uso di un unsafe contesto.

Note

Questa sezione deve probabilmente essere espansa per dichiarare formalmente ciò che ogni espressione e istruzione deve considerare per richiedere un unsafe contesto.

Tipi di puntatore

Come accennato, i puntatori non diventano più intrinsecamente non sicuri. Tutti i riferimenti a contesti non sicuri in §24.3 vengono eliminati. I tipi di puntatore esistono in C# normali e non richiedono unsafe l'esistenza di tali tipi. Le definizioni dei tipi devono essere applicate a §8.1 e alle sezioni seguenti, come altri tipi.

Analogamente, le conversioni dei puntatori devono essere applicate a §10, con riferimenti ai unsafe contesti rimossi.

Analogamente, le espressioni puntatore, ad eccezione dell'accesso indiretto del puntatore, dell'accesso ai membri del puntatore e dell'elemento puntatore, devono essere applicate a §12, con riferimenti ai unsafe contesti rimossi. Nessuna semantica cambia sul significato di queste espressioni; l'unica modifica è che non richiedono più un unsafe contesto da usare.

Per l'accesso indiretto del puntatore, l'accesso ai membri del puntatore e l'accesso agli elementi del puntatore, questi operatori rimangono non sicuri, poiché questi elementi di accesso non sono gestiti dal runtime. Rimangono in §24 e continuano a richiedere l'uso di un unsafe contesto. Qualsiasi uso all'esterno di un unsafe contesto è un errore. Nessuna semantica su questi operatori cambia; continuano a significare esattamente la stessa cosa che fanno oggi. Queste espressioni devono essere sempre presenti in un unsafe contesto.

L'istruzione fissa passa a §13, con riferimenti ai unsafe contesti rimossi.

I puntatori a funzione non sono ancora incorporati nella specifica C# principale, ma sono interessati in modo analogo; tutto, ma la chiamata del puntatore di funzione viene spostata nella specifica standard. Un'espressione di chiamata del puntatore di funzione deve essere sempre presente in un unsafe contesto.

Buffer a dimensione fissa

Il brano per i buffer a dimensione fissa è simile ai puntatori. La definizione di un buffer a dimensione fissa non è pericolosa e passa a §16.3. L'accesso a un buffer a dimensione fissa in un'espressione è analogamente sicuro, a meno che l'espressione non si verifichi come il primary_expression di un oggetto element_access; questi vengono valutati come un pointer_element_access, che non è sicuro, in base alle regole precedenti.

Allocazione nello stack

Anche in questo caso, la storia per l'allocazione dello stack è molto simile ai puntatori. La conversione di un oggetto stackalloc in un puntatore non è più sicura. Si tratta della deferenza di quel puntatore non sicuro. È tuttavia possibile aggiungere una nuova regola:

Un stackalloc_expression non è sicuro se tutte le istruzioni seguenti sono vere:

  • Il stackalloc_expression viene convertito in un Span<T> oggetto o .ReadOnlySpan<T>
  • Il stackalloc_expression non dispone di un stackalloc_initializer.
  • Il stackalloc_expression viene utilizzato all'interno di un membro SkipLocalsInitAttribute applicato.

In questi contesti, lo spazio dello stack risultante potrebbe avere contenuto di memoria sconosciuto e viene convertito in un tipo che fornisce un wrapper sicuro per l'accesso alla memoria non gestita. Ciò viola il contratto di Span<T> e ReadOnlySpan<T>, quindi deve essere soggetto a un esame aggiuntivo da parte dell'autore e revisori di tale codice.

A differenza di altre modifiche alle unsafe regole che sono relax, si tratta di una stretta, e quindi si applica solo sotto il consenso esplicito alle regole di sicurezza della memoria aggiornate per evitare una interruzione.

Note

Ciò significa che l'assegnazione di a stackalloc un puntatore è sempre sicura, indipendentemente dal contesto.

Si noti che un stackalloc oggetto di un tipo gestito rimane un errore.

sizeof

Per alcuni tipi predefiniti, sizeof è sempre stato costante e sicuro (§12.8.19) e rimane invariato. Per altri tipi, sizeof usato per richiedere un contesto unsafe (§24.6.9), ma è ora sicuro indipendentemente dal consenso esplicito alle regole di sicurezza della memoria aggiornate.

Override, ereditarietà e implementazione

Si tratta di un errore di sicurezza della memoria da aggiungere unsafe a livello di membro in qualsiasi override o implementazione di un membro che non è richiesto in origine non sicuro , perché i chiamanti possono usare la definizione di base e non vedere alcuna aggiunta di unsafe da un'implementazione derivata.

Delegati e lambda

Si tratta di un errore di sicurezza della memoria per convertire un membro requires-unsafe in un tipo delegato all'esterno del unsafe contesto. I tipi delegati e i tipi di funzione non possono essere non sicuri. Si tratta di un errore in fase di compilazione da applicare unsafe a un simbolo lambda.

extern

Poiché extern i metodi si trovano in percorsi nativi che non possono essere garantiti dal runtime, il compilatore non può stabilire se sono sicuri o non sicuri. Anche i metodi che accettano unmanaged solo parametri per valore non possono essere chiamati in modo sicuro da C#, perché la convenzione di chiamata usata per il metodo potrebbe essere specificata in modo non corretto dall'utente e deve essere verificata manualmente dalla revisione.

Di conseguenza, in base alle regole di sicurezza della memoria aggiornate, il compilatore richiede che ogni extern metodo venga contrassegnato in modo esplicito come unsafe o safe.

extern I metodi degli assembly che usano le regole di sicurezza della memoria legacy non vengono considerati in modo unsafe implicito perché extern vengono considerati dettagli di implementazione che non fanno parte della superficie pubblica. extern non è garantito che venga mantenuto negli assembly di riferimento.

Si noti che questo è diverso dalla modalità di compatibilità che si applica anche agli assembly legacy-rules perché i metodi con puntatori nella firma richiederebbero sempre un contesto non sicuro nel sito di chiamata.

Modificatori e contesti non sicuri

Oggi, come descritto nella specifica del contesto non sicuro, unsafe si comporta in modo lessicale, contrassegnando l'intero corpo testuale contenuto dal unsafe blocco come unsafe contesto (ad eccezione dei corpi iteratori) e anche alcuni contesti circostanti in caso di dichiarazioni:

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
}

Con il consenso esplicito alle regole di sicurezza della memoria aggiornate, unsafe su un membro lo contrassegna come non sicuro, estendendo l'obbligo di controllo al chiamante e non introduce un unsafe contesto (al contrario, solo le aree esplicite unsafe nel corpo stabiliscono unsafe contesti).

unsafe nelle dichiarazioni seguenti genera un errore perché non ha più un significato:

  • delegate,
  • costruttore statico,
  • Distruttore
  • dichiarazione di tipo (class, structe così via).

unsafein un costruttore introduce un unsafe contesto all'interno del relativo inizializzatore, ad esempio un unsafe costruttore può chiamare un costruttore o un oggetto requires-unsafebase.this

I tipi con costruttori non sicuri senza parametri non soddisfano il new() vincolo . Analogamente e, inoltre, gli struct con parametri a meno che non siano necessari costruttori non sicuri non soddisfano il struct vincolo.

unsafe in un membro non viene applicato ad alcuna funzione anonima o locale annidata all'interno del membro. Lo stesso vale per le funzioni anonime e locali dichiarate all'interno di un unsafe blocco (sono ancora in un unsafe contesto come sempre, ma non diventano unsafe). Per contrassegnare una funzione locale come non sicura, deve essere contrassegnata manualmente come unsafe. Le espressioni lambda non possono essere contrassegnate come requires-unsafe (la unsafe parola chiave non è consentita).

Quando un membro è partial, entrambe le parti devono essere d'accordo unsafe sul modificatore, invariate rispetto alle regole C#.

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
}

Per le proprietà get e set/init le funzioni di accesso possono essere dichiarate in modo indipendente come unsafe; contrassegnando l'intera proprietà come unsafe significa che entrambe le get funzioni di accesso e set/init non sono sicure. Non è attualmente possibile inserire modificatori nelle funzioni di accesso agli eventi e questa proposta non modifica che, ad esempio, add le remove funzioni di accesso agli eventi non possono essere dichiarate in modo indipendente come unsafe. Solo se l'intero evento è contrassegnato come unsafe, significa che le funzioni di accesso non sono sicure; in caso contrario, sono sicure.

Fields

unsafe in un campo lo contrassegna anche come non sicuro e non introduce un unsafe contesto nel relativo inizializzatore. La modalità compatibilità si applica anche ai campi.

Contrassegnare una proprietà o un evento perché unsafe non rende il relativo campo sottostante non sicuro.

In un tipo con [StructLayout(LayoutKind.Explicit)] o [ExtendedLayout], tutti i campi dell'istanza devono essere contrassegnati come safe o unsafe. Se il campo è "nascosto" dietro una proprietà automatica o un evento simile a un campo, il safe/unsafe requisito viene spostato nella proprietà automatica o nell'evento simile al campo.

Metadati

Quando un assembly viene compilato con le nuove regole di sicurezza della memoria, viene contrassegnato con MemorySafetyRulesAttribute (dettagliato di seguito), compilato come 15 versione della lingua. Si tratta di un segnale a tutti i consumer downstream a RequiresUnsafeAttribute cui tutti i membri definiti nell'assembly verranno attribuiti correttamente (descritti in dettaglio) se è necessario chiamare un unsafe contesto. Per qualsiasi membro di tale assembly non contrassegnato con RequiresUnsafeAttribute non è necessario chiamare un unsafe contesto, indipendentemente dai tipi nella firma del membro.

Si tratta di un errore per applicare o RequiresUnsafeAttribute a qualsiasi simbolo in modo esplicito nell'origineMemorySafetyRulesAttribute.

Il compilatore ignora i membri contrassegnati RequiresUnsafeAttributeda assembly che usano le regole di sicurezza della memoria legacy. Viene invece usata la modalità compatibilità .

Quando un membro non di tipo è contrassegnato come unsafe, il compilatore esegue la sintesi di un'applicazione RequiresUnsafeAttribute nel membro nei metadati. Quando un membro non sicuro richiede un utente genera membri nascosti, ad esempio i metodi get/set di una proprietà automatica, sia il membro rivolto all'utente che i membri nascosti generati da tale membro sono tutti non sicuri e RequiresUnsafeAttribute vengono applicati a tutti.

La MemorySafetyRulesAttribute definizione e RequiresUnsafeAttribute viene sintetizzata dal compilatore, se necessario, in base alle regole membro note standard.

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

Modalità compatibilità

Ai fini di compatibilità e per ridurre il numero di falsi negativi che si verificano quando si abilitano le nuove regole, è disponibile una regola di fallback per i moduli che non sono stati aggiornati alle nuove regole. Per tali moduli, un membro viene considerato non sicuro se contiene un puntatore o un tipo di puntatore a funzione in un punto qualsiasi tra i tipi di parametro o il tipo restituito (può essere annidato in un tipo non puntatore, ad esempio int*[]). Si noti che questo non si applica ai puntatori nei tipi di vincolo ,ad esempio where T : I<int*[]>, perché in precedenza non sarebbero necessari contesti non sicuri nei siti di chiamata.

Questo non include parametri generici sostituiti (ad esempio, il metodo I<T>.M(T) quando viene T sostituito per int*[]) perché non esiste un modo sicuro per il membro di destinazione di usare comunque tale tipo di puntatore.

Tale modalità di compatibilità richiede che i membri non sicuri richiedano l'uso di un unsafe contesto anche da parte dei chiamanti che non hanno accodato esplicitamente le regole aggiornate di sicurezza della memoria. Ciò dovrebbe evitare un "dip" in cui solo l'aggiornamento di LangVersion (ma non l'aggiornamento della versione delle regole di sicurezza della memoria) rende la maggior parte delle operazioni di puntatore sicure (incluse le funzioni di chiamata con puntatori nella firma che probabilmente verranno contrassegnate come non sicure quando si è scelto di acconsentire esplicitamente alle regole aggiornate) e quindi rendendo il codice meno protetto in questa finestra di migrazione.

VB

Non è necessario aggiungere il supporto a Visual Basic per i membri non sicuri perché attualmente non unsafe sono presenti contesti in VB e non è possibile usare i puntatori.

unsafe Espressioni

Un'espressione unsafe introduce un contesto minimo unsafe per la valutazione di una singola espressione. È utile nelle situazioni in cui un unsafe blocco potrebbe inutilmente ampliare l'ambito unsafe o in cui unsafe i blocchi non possono essere usati sintatticamente (ad esempio nei catch filtri, inizializzatori di campo, inizializzatori di costruttori e posizione dell'operando di await).

Sintassi

Un unsafe_expression oggetto viene aggiunto come primary_no_array_creation_expression:

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

È soggetto allo stesso AllowUnsafeBlocks requisito della unsafe parola chiave altrove.

Semantica

Un unsafe_expression oggetto stabilisce un unsafe contesto per la valutazione della relativa espressione. Ciò significa che la dereferenziazione del puntatore, le chiamate ai puntatori di funzione e le chiamate ai membri non sicuri richiesti sono tutte consentite all'interno dell'espressione racchiusa. Il tipo e il valore di unsafe_expression sono il tipo e il valore dell'espressione racchiusa.

Il unsafe contesto stabilito da un unsafe_expression oggetto non si estende oltre la parentesi chiusa.

Esempi di motivazione e migrazione

Diverse posizioni sintattiche non ammettono unsafe affatto blocchi, ma possono contenere sottoespressioni che chiamano membri non sicuri . Senza unsafe espressioni, la migrazione di questo codice richiede l'estrazione dell'espressione secondaria non sicura in una funzione locale helper o in una variabile temporanea, che nasconde la finalità e aumenta la dettaglio.

await in un metodo richiede unsafe . Un'espressione await non può essere visualizzata all'interno di un unsafe blocco. Quando il metodo in attesa diventa non sicuro, è necessaria una variabile temporanea per contenere l'attività prima che possa essere attesa:

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

Intercettare i filtri. Il when filtro di una catch clausola è un'espressione, non un corpo dell'istruzione. Un unsafe blocco può racchiudere solo le istruzioni, quindi non è possibile posizionarne una intorno solo all'espressione di filtro. Inoltre, se il try corpo contiene un'espressioneawait, un unsafe blocco non può racchiudere l'intera istruzione,await non è consentito all'internocatchtry/di un unsafe blocco. Quando un metodo usato in un filtro diventa unsafe, l'unica alternativa senza unsafe espressioni è un 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)))
{
}

Inizializzatori di campo. In base alle regole aggiornate, unsafe in un campo non introduce un unsafe contesto nel relativo inizializzatore. Quando l'inizializzatore di un campo chiama un membro requires-unsafe , un'espressione unsafe fornisce il contesto senza richiedere un metodo helper:

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

Inizializzatori del costruttore. this(...) e base(...) gli elenchi di argomenti dell'inizializzatore sono espressioni, non corpi di istruzione. Se uno di questi argomenti chiama un membro non sicuro richiesto , non è più possibile inserire un unsafe blocco, pertanto la chiamata deve essere spostata in un helper:

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

Chiamate inline. Più in generale, il wrapping solo dell'espressione secondaria requires-unsafe mantiene stretto l'ambito di controllo ed evita il pull del codice sicuro circostante (ad esempio la valutazione degli argomenti o una chiamata al metodo contenitore) nel unsafe contesto:

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

Domande

(risposto) Usare RequiresUnsafeAttribute per indicare i membri non sicuri

Anziché usare la unsafe parola chiave nel membro per indicare i membri non sicuri , è possibile usare un attributo (RequiresUnsafeAttribute) applicato al membro (e non modificare il significato del unsafe modificatore nei membri).

Vantaggi di unsafe:

  • simile ad altri linguaggi e quindi più facile da comprendere,
  • più individuabile di un attributo.

Vantaggi di un attributo (o di un'altra parola chiave):

  • evita l'interruzione dei membri esistenti contrassegnati come unsafe,
  • adozione incrementale possibile (membro per membro),
  • non forza il contrassegno dell'intero corpo come unsafe (anche con unsafe la parola chiave potremmo cambiareunsafe per non avere un effetto sui corpi),
  • consente di eliminare tutti gli errori non sicuri richiesti senza la necessità di contrassegnare il membro stesso come requires-unsafe (esempi).

Discussioni:

Risposta: usare la parola chiave unsafe per indicare i membri non sicuri .

Funzioni locali/contesti sicuri lambda

Al momento unsafe su un corpo del metodo è l'ambito lessicale. Tutte le funzioni locali o le espressioni lambda annidate ereditano questa situazione e i relativi corpi si trovano in un contesto non sicuro per la memoria. È questo comportamento che vogliamo mantenere nella lingua? Si noti che se si mantiene unsafe come modificatore usato per esporre che il chiamante deve essere non sicuro, questo potrebbe avere effetti sulla firma del metodo. Come attualmente proposto, le funzioni anonime e locali annidate non mantengono il contesto non sicuro del membro contenitore.

unsafeTipo delegato ty

È possibile contrassegnare i tipi delegati e le espressioni lambda (e i tipi di funzione) in base a esigenze non sicure. Ciò richiederebbe diverse regole aggiuntive (all'esterno unsafe del contesto):

  • disallow using requires-unsafe delegates as type arguments,
  • disallow converting those delegates to anything that's not requires-unsafe (Delegate,Expression, and object), Without this, there is a risk of force unsafe annotations in the wrong spot and having an area where the real area of unsafety is not correttamente called out.

Conversione di gruppi lambda/metodo in tipi delegati sicuri

Se si consentono unsafe espressioni lambda e delegati, è consigliabile eseguire la conversione di un gruppo di metodi o lambda non sicuro in un tipo di delegato non richiesto-non sicuro consentito senza avviso o errore in un unsafe contesto? Se non lo facciamo, potrebbe essere piuttosto doloroso per varie parti dell'ecosistema, in particolare eventuali enumerabili che vengono passate tramite query LINQ.

Tipi naturali del gruppo lambda/metodo

Oggi, l'unico impatto reale sulla semantica e sul codegen (oltre ai metadati aggiuntivi) sta modificando la function_type di un gruppo di metodi o lambda quando unsafe si trova nella firma. Se si dovesse evitare di eseguire questa operazione, non vi sarebbe alcun impatto reale su entrambi, che potrebbe dare agli adottanti maggiore fiducia che il comportamento non sia cambiato in modo subtly sotto le quinte.

Note

Se si decide di mantenere la possibilità di avere unsafe espressioni lambda, è necessario aggiornare questa proposta per includere una modifica della sintassi per consentire la dichiarazione unsafe delle espressioni lambda in primo luogo.

Puntatori a tipi gestiti

C# 11 consente ai puntatori di gestire i tipi con un avviso. È consigliabile rilassare l'avviso per le operazioni relative all'indirizzo? Pensiamo che il problema sia solo quando l'utente dereferenzia tale puntatore, che rientra nelle normali regole di evoluzione non sicura. Ma che ne dici sizeofdi ?

stackalloc come inizializzato

Oggi , la specifica considera stackalloc sempre la memoria non inizializzata e indica che il contenuto non è definito a meno che non venga cancellato o assegnato manualmente. Si consideri questo bug specifico o è necessario modificare ciò che si prende in considerazione unsafe per stackalloc scopi?

stackalloc Regola

LDM deve confermare la stackalloc regola definita in precedenza e se deve essere applicata indipendentemente dal consenso esplicito come altre modifiche correlate al puntatore.

AllowUnsafeBlocks

Significato di AllowUnsafeBlocks è attualmente invariato: è necessario impostare su true per poter usare la unsafe parola chiave o SkipLocalsInitAttribute. È consigliabile non richiederlo per SkipLocalsInitAttribute le regole aggiornate perché il BCL può contrassegnare l'attributo come require-unsafe? È necessario richiederlo anche per la safe parola chiave ? È necessario richiederlo sia per i blocchi unsafe che unsafe per le dichiarazioni di membri o per altre combinazioni di tali elementi?

(risposto) unsafe Espressioni

Altri linguaggi con caratteristiche più complete unsafe hanno aggiunto unsafe come espressione che migliora l'ergonomicità degli utenti e consente agli autori di limitare più precisamente dove unsafe viene usato. Si tratta di un elemento che si vuole avere in C#? Si consideri una chiamata inline a un unsafe membro che gestisce direttamente la sicurezza: in questo momento, l'autore dovrà eseguire il wrapping dell'intera istruzione in un unsafe blocco, espandendo l'ambito del unsafe contesto oppure sarebbe necessario suddividere la chiamata della funzione interna in una variabile intermedia.

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

Risposta: . Vedere la sezione di progettazione dettagliata.

Più unsafe contesti e relax

È consigliabile ridurre le restrizioni relative ai unsafe parametri del puntatore e agli iteratori e ai metodi asincroni? In particolare, consentire await UnsafeMethod() sarebbe utile perché ora gli utenti devono riscrivere in Task t; unsafe { t = UnsafeMethod(); } await t;. Per altri dettagli , vedere ref/unsafe in iterators/async .

Dobbiamo anche consentire &UnsafeMethod in un contesto sicuro? Attualmente, poiché la proposta è in piedi, questo richiede unsafe contesto se il metodo è contrassegnato come unsafe. Ma dal momento che stiamo semplicemente ottenendo il suo indirizzo, che dovrà essere unsafe contesto quando dereferenziato/chiamato, potremmo consentire l'indirizzo di se stesso in un contesto sicuro.

Relax non sicuri gated on LangVersion

Dobbiamo rendere il contesto non sicuro relax incondizionato su LangVersion?

  • LDM 2026-04-06: non sono condizionali per la versione delle regole di sicurezza della memoria
  • TODO: che ne dici di LangVersion?

(risposto) unsafe sui tipi

Non è possibile considerare che l'intero ambito lessicale di un unsafe tipo sia un unsafe contesto e avvisare per un unsafe oggetto su un tipo perché non avrebbe alcun significato.

  • LDM 2025-11-12: unsafe in un tipo non avrà alcun significato
  • LDM 2026-05-13: si tratta di un errore (può essere rivisitato in base al feedback)

Risposta: unsafe in un tipo è un errore (può essere rivisitato in base al feedback) nelle regole aggiornate.

unsafe nelle funzioni di accesso

Le funzioni di accesso alle proprietà sono appena consentite unsafe , ma non nelle funzioni di accesso agli eventi, in linea con altri modificatori preesistenti. Ciò significa anche che unsafe in una proprietà è sufficiente un collegamento per unsafe sulle relative funzioni di accesso. Tuttavia, allo stesso tempo, partialè necessario che i unsafe modificatori corrispondano:

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

Forse dovrebbe comportarsi in modo analogo a readonly, ad esempio, non consentire unsafe sia la proprietà che la relativa funzione di accesso contemporaneamente:

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

(risposto) Consentire l'eliminazione di errori non sicuri in scenari di casi perimetrali

In che modo è consigliabile eliminare gli errori non sicuri necessari negli scenari seguenti?

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

Per eliminare gli errori non sicuri necessari, è necessario introdurre in qualche modo un unsafe contesto nella firma di tali membri. Ma la unsafe parola chiave non introduce più un unsafe contesto. È possibile introdurre unsafe un unsafe contesto nella firma, ma forzare la creazione di un costruttore non sicuro quando si vuole chiamare un costruttore di base non sicuro sembra sfortunato. È possibile eseguire utilizzi non sicuri delle firme che gli utenti potrebbero eliminare sul posto se raggiungono questi rari casi limite.

Si è verificato un problema simile a quello dei tipi, perché unsafe in essi non viene introdotto unsafe alcun contesto:

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:
    • codice non eseguibile, ad esempio l'applicazione di attributi, deve rimanere un errore nonpressibile (fino a quando non si riceve un feedback)
    • altri casi perimetrali come new() possono anche rimanere un errore fino a quando non si riceve feedback
    • non consentire la dichiarazione di parametri a meno che non siano necessari costruttori non sicuri
    • solo l'inizializzatore di un unsafe costruttore può chiamare un costruttore o thisun oggetto requires-unsafebase

raccolte params

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

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

Questa dichiarazione può essere semplicemente consentita perché la chiamata di tale metodo richiede comunque un unsafe contesto perché la raccolta non sicura params viene costruita nel sito di chiamata. Anche se ciò rende effettivamente la dichiarazione non sicura, quindi potremmo semplicemente richiedere l'annotazione unsafe o almeno avvisare su tale fatto. Si noti che il caso della params raccolta è un errore attualmente in linea con il comportamento di altre funzionalità simili (Obsolete, UnmanagedCallersOnly), ma potrebbe trattarsi di un bug di implementazione.

Membri noti

Per semplicità e integrità dell'implementazione, si propone che il compilatore sia libero di presupporre che tutti i membri noti (ad esempio Array.Length) possano essere considerati sicuri (ad esempio, non richiedono unsafe).

Documentazione XML

Il passaggio di un obbligo ai chiamanti ha la responsabilità di rendere chiaro ciò che è tale obbligo. Dobbiamo formalizzare questa operazione oltre a ciò che può essere già rappresentato nella documentazione xml?

I membri contrassegnati unsafe devono avere commenti che indicano cosa è necessario per il chiamante per assicurarsi che il codice sia corretto. Per semplificare la visualizzazione e la facilità di differenziazione nella documentazione, sarebbe utile un nuovo tag di documentazione XML: <safety />. Ci si aspetterebbe che tutte le pre/post-condizioni verrebbero inserite nel <safety> blocco.

Per documentare il ragionamento di ogni unsafe blocco, è consigliabile usare // SAFETY commenti simili a Rust. È anche consigliabile controllarli dal compilatore (ad esempio, avere un avviso disattivato per impostazione predefinita) o lasciarlo a un analizzatore?

Avvisi senza significato unsafe

Più dichiarazioni devono produrre l'avviso o l'errore senza unsafe significato? Ad esempio, i metodi con corpi vuoti (o extern) e così via. È già disponibile un analizzatore dell'IDE per non necessario unsafe .

Dovrebbe [ModuleInitializer] unsafe void M() { } essere un errore, in modo analogo a un costruttore statico?

(risposto) unsafe campi

Oggi non è stata fatta unsafe alcuna proposta su un campo. Potrebbe essere necessario aggiungerlo, in modo che qualsiasi lettura o scrittura in un campo contrassegnato come unsafe deve trovarsi in un unsafe contesto. In questo modo è possibile annotare meglio le preoccupazioni relative al codice, ad esempio:

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

È anche necessario contrassegnare il campo sottostante della proprietà automatica come unsafe?

Per essere coerenti con la nostra decisione per i membri, sarebbe bene prendere unsafe su un campo anche non introdurre un unsafe contesto. Se nell'inizializzatore del campo vengono usate operazioni non sicure , l'utente può sempre incapsularli in un metodo o introdurre unsafe espressioni.

  • LDM 2026-05-13: i campi possono essere contrassegnati come non sicuri tramite unsafe; gli inizializzatori non sono nel unsafe contesto.

(risposto) Layout esplicito

I campi negli struct devono essere contrassegnati come [StructLayout(Explicit)] o [ExtendedLayout] devono essere contrassegnati come unsafe?

Raccomandazione: sì.

  • LDM 2026-05-13: sì, richiedono unsafe o safe, esattamente come per externs.

Layout esplicito e campi di supporto

Se il compilatore sintetizza un campo sottostante per una proprietà automatica o un evento simile a un campo in un Explicit/Extended tipo, è necessario invece per safe/unsafe la proprietà o l'evento? In caso contrario, l'utente sarà costretto a espandere queste dichiarazioni auto in campi manuali e dichiarazioni di membri wrapper. Che ne dici di un parametro del costruttore primario che ottiene un campo sottostante? Sia safe che unsafe il modificatore non sono attualmente consentiti per una dichiarazione di parametro.

[Out] e [SkipLocalsInit]

Poiché ad esempio VB non garantisce che [Out] i parametri vengano inizializzati, in combinazione con [SkipLocalsInit], la chiamata di tali parametri può essere considerata unsafe in C#. D'altra parte, sembra che il problema del chiamato non rispetti il suo [Out] contratto (analogamente potrebbe non essere sicuro in molti altri modi).

Se si decide che questi casi devono essere unsafe, è possibile escludere i metodi di cui è stato scelto il consenso esplicito (attualmente quelli provengono da C# che garantisce l'utilizzo corretto di [Out] ma se altri linguaggi implementano le nuove regole, dovrebbero garantire anche tale comportamento).

Accettare l'indirizzo di una variabile non inizializzata

Oggi, prendendo l'indirizzo di una variabile non sicuramente assegnata può considerare che variabile sicuramente assegnata, esponendo un membro non inizializzato. Sono disponibili due opzioni per risolvere questo problema:

  1. Richiedere che le variabili vengano assegnate in modo definitivo prima di consentire l'uso di un operatore address-of.
  2. Prendere l'indirizzo di una variabile non inizializzata non sicura.

Esempi:

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

Valore di MemorySafetyRulesAttribute

Quale deve essere la versione delle regole di sicurezza della memoria "enabled"/"updated"? 2? 15? 11? Vedere anche Sdk Unsafe Adoption and More gradual opt-in?.

(risposto) Consenso esplicito più graduale?

Oggi, il consenso esplicito offre due elementi in una sola volta: l'applicazione delle regole non sicure nel codice e un segnale pubblicato ai consumer (tramite un attributo a livello di assembly) che le annotazioni sono intenzionali. È possibile che gli utenti vogliano iniziare a ottenere la diagnostica di imposizione durante l'annotazione, senza essere pronti a pubblicare che hanno annotato completamente l'assembly. È necessario disporre di un livello di consenso esplicito "intermedio" che potrebbe far emergere la diagnostica non sicura come avvisi e il consenso esplicito completo li promuoverebbe agli errori?

(risposto) Consenso esplicito più dettagliato

Fornire un meccanismo di consenso esplicito basato su area fine analogo a quello creato per i tipi riferimento nullable, in cui gli utenti possono usare direttive per abilitare la funzionalità per aree specifiche del codice sorgente? Vedere anche Acconsentire esplicitamente o rifiutare esplicitamente le aree del codice.

(risposto) extern in modo implicito non sicuro

Questo è attualmente l'unico luogo in cui RequiresUnsafeAttribute viene sintetizzato senza una parola chiave esplicita unsafe . Stiamo bene con questo outlier?

CoreLib espone anche molti metodi extern (FCalls) come sicuri oggi. Il trattamento dei metodi extern come non sicuri in modo implicito richiede il wrapping dei metodi extern implicitamente non sicuri con un wrapper sicuro. È possibile che si verifichino situazioni in cui l'aggiunta del wrapper aggiuntivo è difficile a causa dei dettagli di implementazione del runtime.

  • LDM 2026-04-01: extern i membri devono essere contrassegnati in modo esplicito come sicuri o non sicuri
  • LDM 2026-04-06: stessa decisione ribadito
  • LDM 2026-04-13: decisione temporanea di usare safe la parola chiave
  • LDM 2026-05-13: parola chiave use safe (ancora aperta per la revisione)

Risposta: extern i membri devono essere contrassegnati unsafe o safe (aggiungiamo una nuova parola chiave per esso, ma possono rivisitarlo prima che la funzionalità venga fornita, in base al feedback).

(risposto) unsafe impostazioni predefinite del contesto nei membri

Non è possibile considerare l'esecuzione automatica dell'intero corpo di un metodo in un unsafeunsafe contesto. Rust ha fatto questo in RFC 2585, con la motivazione che aiuta a ridurre l'ambito dei unsafe blocchi alle posizioni in cui unsafe viene effettivamente usato. È possibile eseguire la stessa operazione in C#, come avviso o errore, con motivazioni simili.

(risposto) new() vincolo

Si vuole supportare new() con requires-unsafe (qualcosa che attualmente non sembra supportare nel compilatore per altre funzionalità, ad esempio 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: i tipi con costruttori senza parametri requires-unsafe non devono soddisfare il new() vincolo

new() vincolo e usings

Come deve comportarsi negli alias e negli usi statici?

  • Se si tratta di un errore nella using dichiarazione, soppressibile tramite la unsafe parola chiave già supportata o
  • dovrebbe essere un errore normalmente nel sito di utilizzo come se fosse usato direttamente senza un alias o un uso statico?

Note

Nel secondo caso, è necessario aggiungere un avviso "senza unsafesignificato" per l'uso di alias e using statici.

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

Si noti che altri vincoli si comportano come l'opzione precedente oggi:

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

Dovrebbero essere unsafepiù costrutti ?

  • dynamic (probabilmente dovrebbe corrispondere a ciò che il BCL decide per le API di reflection)

(risposto) Come rompere vogliamo asimmetrie

Testo della domanda

La proposta iniziale è un approccio di massima rottura, soprattutto come prova di fiamma per quanto aggressivo vogliamo essere. Non propone alcuna possibilità di acconsentire esplicitamente o rifiutare le sezioni del codice, modifica il significato di unsafe sui metodi, impedisce l'uso di unsafe su tipi, usa errori anziché avvisi e in genere forza la migrazione a verificarsi tutti contemporaneamente, al momento dell'aggiornamento del compilatore (e quindi potenzialmente ripetutamente come aggiornamento delle dipendenze e aggiunta unsafe ai membri che erano già in uso). Tuttavia, abbiamo una vasta esperienza nel apportare modifiche come questa che possiamo trarre per definire le dimensioni delle interruzioni e consentire l'adozione incrementale. Queste opzioni sono illustrate di seguito.

Acconsentire esplicitamente/rifiutare le aree di codice

Non è la prima volta che C# ha ridefinito il caso "base" del codice non annotato. C# 8.0 ha introdotto la funzionalità del tipo riferimento nullable, che in molti modi può essere vista come un progetto per il modo in cui la unsafe funzionalità sta formando. Ha avuto obiettivi simili (evitare bug che costano miliardi di dollari ridefinendo il modo in cui viene interpretato C# predefinito) e un set di funzionalità generale simile (aggiungere nuove informazioni ai tipi per propagare gli stati ed evitare bug). Era anche molto importante e aveva bisogno di un forte set di funzionalità di consenso esplicito e rifiuto esplicito per consentire l'adozione della funzionalità nel tempo da codebase. Tale funzionalità è il "contesto del tipo riferimento nullable". Si tratta di un ambito lessicale che informa il compilatore, per una determinata area nel codice, sia come interpretare i riferimenti di tipo non annotati e quali tipi di avvisi fornire all'utente. È possibile usarlo anche come modello per unsafe l'aggiunta di un "contesto delle regole di sicurezza" o simile a quello per consentire il controllo dell'applicazione o meno di queste nuove regole.

Un vantaggio che abbiamo con le nuove unsafe funzionalità è che sono molto meno prevalenti. Sebbene ci sia un numero decente di unsafe chiamate nelle librerie principali, i nostri indovinano la percentuale di librerie principali che usano unsafe è molto inferiore a "ogni singola riga di codice C# mai scritto". Speriamo che questo significa che, anche se è possibile acconsentire esplicitamente o rifiutare esplicitamente, non è necessario un meccanismo complicato quanto nullable, con commutatori preprocessore dedicati e simili.

Avvisi e errori

La proposta indica attualmente che i requisiti di sicurezza della memoria sono attualmente applicati tramite un avviso, anziché un errore. Si tratta di un'esperienza di utilizzo della funzionalità nullable, in cui gli avvisi hanno consentito alle codebase di adottare in modo incrementale la nuova funzionalità e non devono convertire contemporaneamente grandiwate di codice. Ci aspettiamo che sia necessario un processo simile per gli avvisi non sicuri: molte codebase saranno semplicemente in grado di attivare le nuove regole a livello globale e procedere con la propria vita. Tuttavia, ci aspettiamo che le codebase di cui ci preoccupiamo maggiormente per l'adozione delle nuove regole avranno grandi quantità di codice da annotare e vogliamo che siano in grado di procedere con la funzionalità, invece di vedere un muro di errori e rinunciare immediatamente. Facendo in modo che gli avvisi dei requisiti consentano a queste codebase di correggere gli avvisi in base al file o al metodo in base alle esigenze, disabilitando gli avvisi in qualsiasi altro luogo.

Interruzioni di firma del metodo

In questo momento, si propone che unsafe come parola chiave sul metodo si sposta da un oggetto con ambito lessicale senza un impatto semantico a qualcosa che ha un impatto semantico e non è lessicalmente con ambito. È possibile limitare questa interruzione introducendo una nuova parola chiave per quando il chiamante di un metodo o di un membro deve trovarsi in un unsafe contesto, callerunsafe ad esempio come modificatore.

Impostazioni predefinite per i generatori di origine

Per nullable, gli autori del generatore devono acconsentire esplicitamente a nullable indipendentemente dal fatto che l'intero progetto abbia acconsento esplicitamente alla funzionalità per impostazione predefinita, in modo che l'output del generatore non venga interrotto dall'utente attivando nullable e avvisando come errore. Dobbiamo fare lo stesso per i generatori di origine?

Conclusione

Risposta in LDM 2025-11-05. Verranno segnalati errori per i problemi di sicurezza della memoria quando vengono attivate le nuove regole e non verranno apportate eccezioni per i generatori di origine.

(risposto) Errori o avvisi?

(risposto) Inviti del generatore di origine

(risposto) Richiedere safe l'autore per i membri con unsafe blocchi o puntatori?

(risposto) Modalità di compatibilità anche per i chiamanti non acconsentiti?

Risposta: i membri senza consenso esplicito con puntatori nella firma sono considerati non sicuri per i chiamanti acconsentiti esplicitamente.

(risposto) Estendere la modalità compatibilità?

Dovremmo considerare nint e System.IntPtr anche come puntatori? È consigliabile considerare extern/DllImport anche i chiamanti senza consenso esplicito come richiesto-non sicuro ? Dovrebbe essere visualizzato un avviso generale quando un assembly di consenso esplicito fa riferimento a un assembly non consenso esplicito?

  • LDM 2026-04-29: nessuna estensione (gli analizzatori potrebbero coprire alcuni segnali meno sicuri)