Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Nota
Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tal differenze vengono acquisite nelle note pertinenti del language design meeting (LDM) .
Nell'articolo sulle specifiche di puoi trovare ulteriori informazioni riguardanti il processo di adozione degli speclet delle caratteristiche nello standard del linguaggio C#.
Problema del campione: https://github.com/dotnet/csharplang/issues/1331
Sommario
Unificare il comportamento tra iteratori e metodi asincroni. Specificamente:
- Consenti
ref/ref structlocali e blocchiunsafenegli iteratori e nei metodi asincroni, a condizione che siano usati in segmenti di codice senzayieldoawait. - Avvisa
yieldall'interno dilock.
Motivazione
Non è necessario impedire ref/ref struct locali e blocchi in unsafe nei metodi asincroni/iteratori se non vengono usati in yield o await, perché non devono essere sollevati.
async void M()
{
await ...;
ref int x = ...; // error previously, proposed to be allowed
x.ToString();
await ...;
// x.ToString(); // still error
}
Modifiche che rompono
Non sono state apportate modifiche significative nella specifica del linguaggio, ma c'è una modifica di rilievo nell'implementazione di Roslyn (a causa di una violazione della specifica).
Roslyn viola la parte della specifica che indica che gli iteratori introducono un contesto sicuro (§13.3.1).
Ad esempio, se è presente un unsafe class con un metodo iteratore che contiene una funzione locale, tale funzione locale eredita il contesto non sicuro dalla classe , anche se dovrebbe essere stato in un contesto sicuro in base alla specifica a causa del metodo iteratore.
In effetti, l'intero metodo iteratore ha ereditato il contesto non sicuro in Roslyn, è stato semplicemente non consentito usare eventuali costrutti non sicuri negli iteratori.
In LangVersion >= 13, gli iteratori introdurranno correttamente un contesto sicuro perché vogliamo consentire costrutti non sicuri negli iteratori.
unsafe class C // unsafe context
{
System.Collections.Generic.IEnumerable<int> M() // an iterator
{
yield return 1;
local();
async void local()
{
int* p = null; // allowed in C# 12; error in C# 13 (breaking change)
await Task.Yield(); // error in C# 12, allowed in C# 13
}
}
}
Nota:
- Il problema dell'interruzione può essere aggirato semplicemente aggiungendo il modificatore
unsafealla funzione locale. - Ciò non influisce sulle espressioni lambda perché "ereditano" il "contesto iteratore" e pertanto non è stato possibile usare costrutti non sicuri all'interno di essi.
Progettazione dettagliata
Le modifiche seguenti sono legate a LangVersion, ovvero, C# 12 e versioni precedenti continueranno a non consentire variabili locali simili a ref e blocchi unsafe nei metodi asincroni e iteratori, mentre C# 13 rimuoverà queste restrizioni come descritto di seguito.
Tuttavia, i chiarimenti delle specifiche che corrispondono all'implementazione esistente di Roslyn devono essere applicati in tutte le "LangVersions".
Un blocco che contiene una o più istruzioni
yield(§13.15) viene chiamato blocco iteratore, anche se tali istruzioniyieldsono contenute solo indirettamente in blocchi annidati (esclusi espressioni lambda annidate e funzioni locali).[...]
È un errore di compilazione che un blocco iteratore contenga un contesto unsafe (§23.2). Un blocco iteratore definisce sempre un contesto sicuro, anche quando la relativa dichiarazione è annidata in un contesto non sicuro.Il blocco iteratore usato per implementare un iteratore (§15.14) definisce sempre un contesto sicuro, anche quando la dichiarazione dell'iteratore è annidata in un contesto non sicuro.
Da questa specifica deriva anche:
- Se una dichiarazione di iteratore è contrassegnata con il modificatore
unsafe, la firma si trova in un ambito non sicuro, ma il blocco iteratore usato per implementare tale iteratore definisce ancora un ambito sicuro. - La funzione di accesso
setdi una proprietà iteratore o di un indicizzatore (ad esempio, la relativa funzione di accessogetviene implementata tramite un blocco iteratore) "eredita" il relativo ambito sicuro/non sicuro dalla dichiarazione. - Ciò non influisce sulle dichiarazioni parziali senza implementazione perché sono solo firme e non possono avere un corpo iteratore.
Si noti che in C# 12 si tratta di un errore per avere un metodo iteratore contrassegnato con il modificatore unsafe, ma consentito in C# 13 a causa della modifica della specifica.
Per esempio:
using System.Collections.Generic;
using System.Threading.Tasks;
class A : System.Attribute { }
unsafe partial class C1
{ // unsafe context
[/* unsafe context */ A]
IEnumerable<int> M1(
/* unsafe context */ int*[] x)
{ // safe context (this is the iterator block implementing the iterator)
yield return 1;
}
IEnumerable<int> M2()
{ // safe context (this is the iterator block implementing the iterator)
unsafe
{ // unsafe context
{ // unsafe context (this is *not* the block implementing the iterator)
yield return 1; // error: `yield return` in unsafe context
}
}
}
[/* unsafe context */ A]
unsafe IEnumerable<int> M3(
/* unsafe context */ int*[] x)
{ // safe context
yield return 1;
}
[/* unsafe context */ A]
IEnumerable<int> this[
/* unsafe context */ int*[] x]
{ // unsafe context
get
{ // safe context
yield return 1;
}
set { /* unsafe context */ }
}
[/* unsafe context */ A]
unsafe IEnumerable<int> this[
/* unsafe context */ long*[] x]
{ // unsafe context (the iterator declaration is unsafe)
get
{ // safe context
yield return 1;
}
set { /* unsafe context */ }
}
IEnumerable<int> M4()
{
yield return 1;
var lam1 = async () =>
{ // safe context
// spec violation: in Roslyn, this is an unsafe context in LangVersion 12 and lower
await Task.Yield(); // error in C# 12, allowed in C# 13
int* p = null; // error in both C# 12 and C# 13 (unsafe in iterator)
};
unsafe
{
var lam2 = () =>
{ // unsafe context, lambda cannot be an iterator
yield return 1; // error: yield cannot be used in lambda
};
}
async void local()
{ // safe context
// spec violation: in Roslyn, this is an unsafe context in LangVersion 12 and lower
await Task.Yield(); // error in C# 12, allowed in C# 13
int* p = null; // allowed in C# 12, error in C# 13 (breaking change in Roslyn)
}
local();
}
public partial IEnumerable<int> M5() // unsafe context (inherits from parent)
{ // safe context
yield return 1;
}
}
partial class C1
{
public partial IEnumerable<int> M5(); // safe context (inherits from parent)
}
class C2
{ // safe context
[/* unsafe context */ A]
unsafe IEnumerable<int> M(
/* unsafe context */ int*[] x)
{ // safe context
yield return 1;
}
unsafe IEnumerable<int> this[
/* unsafe context */ int*[] x]
{ // unsafe context
get
{ // safe context
yield return 1;
}
set { /* unsafe context */ }
}
}
dichiarazioni di variabili locali di riferimento (13.6.2.4):
Si tratta di un errore in fase di compilazione per dichiarare una variabile locale ref o una variabile di un tipoSi tratta di un errore in fase di compilazione per dichiarare e usare (anche in modo implicito nel codice sintetizzato dal compilatore) una variabile locale ref o una variabile di un tiporef struct, all'interno di un metodo dichiarato con il method_modifierasynco all'interno di un iteratore (§15.14).ref structtra espressioniawaito istruzioniyield return. Più precisamente, l'errore è basato sul meccanismo seguente: dopo un'espressioneawait(§12.9.8) o un'istruzioneyield return(§13.15), tutte le variabili locali di riferimento e le variabili di un tipo diref structnell'ambito vengono considerate sicuramente non firmate (§9,4).
Si noti che questo errore non viene eseguito il downgrade a un avviso in contesti di unsafe come altri errori di sicurezza di riferimento.
Ciò è dovuto al fatto che queste variabili locali di tipo ref non possono essere modificate nei contesti di unsafe senza basarsi sui dettagli di implementazione di come funziona la riscrittura della macchina a stati, pertanto questo errore non rientra nei limiti di ciò che si vuole declassare a avvisi nei contesti di unsafe.
§15.14.1 Iteratori > Generale:
Quando un membro della funzione viene implementato usando un blocco iteratore, è un errore di compilazione se l'elenco di parametri formali del membro della funzione specifica qualsiasi
in,ref readonly,out, o un parametrorefoppure un parametro di un tiporef structo di un tipo di puntatore.
Non è necessaria alcuna modifica nella specifica per consentire blocchi unsafe che non contengono awaitnei metodi asincroni, perché la specifica non ha mai consentito blocchi unsafe nei metodi asincroni.
Tuttavia, la specifica avrebbe sempre dovuto vietare await all'interno di blocchi unsafe (aveva già vietato yield in unsafe in §13.3.1 come indicato in precedenza), quindi proponiamo la seguente modifica alla specifica:
§15.15.1 Funzioni asincrone > General:
Si tratta di un errore in fase di compilazione per l'elenco di parametri formali di una funzione asincrona per specificare qualsiasi parametro
in,outorefo qualsiasi parametro di un tiporef struct.È un errore in fase di compilazione per un contesto non sicuro (§23.2) per contenere un'espressione
await(§12.9.8) o un'istruzioneyield return(§13.15).
§23.6.5 L'operatore di indirizzamento:
Verrà segnalato un errore in fase di compilazione per l'acquisizione dell'indirizzo di una variabile locale o di un parametro in un iteratore.
Attualmente, prendere l'indirizzo di un locale o di un parametro in un metodo asincrono è un avviso nella nuova ondata di avvisi C# 12.
Si noti che più costrutti possono funzionare grazie a ref consentiti all'interno di segmenti, senza await e yield, nei metodi asincroni/iteratori, anche se non è necessaria alcuna modifica specifica per loro, poiché tutto deriva dalle modifiche specifiche precedentemente indicate.
using System.Threading.Tasks;
ref struct R
{
public ref int Current { get { ... }};
public bool MoveNext() => false;
public void Dispose() { }
}
class C
{
public R GetEnumerator() => new R();
async void M()
{
await Task.Yield();
using (new R()) { } // allowed under this proposal
foreach (var x in new C()) { } // allowed under this proposal
foreach (ref int x in new C()) { } // allowed under this proposal
lock (new System.Threading.Lock()) { } // allowed under this proposal
await Task.Yield();
}
}
Alternative
ref/ref structvariabili locali possono essere consentite solo in blocchi (§13.3.1) che non contengonoawait/yield:// error always since `x` is declared/used both before and after `await` { ref int x = ...; await Task.Yield(); x.ToString(); } // allowed as proposed (`x` does not need to be hoisted as it is not used after `await`) // but alternatively could be an error (`await` in the same block) { ref int x = ...; x.ToString(); await Task.Yield(); }yield returnall'interno dilockpotrebbe essere un errore (ad esempioawaitall'interno dilock) o un avviso di tipo warning, ma causerebbe un'interruzione: https://github.com/dotnet/roslyn/issues/72443. Si noti che il nuovoLock-object-basedlocksegnala errori in fase di compilazione peryield returnnel suo corpo, perché questa istruzionelockè equivalente a unusingsu unref structche non consenteyield returnnel suo corpo.Le variabili all'interno di metodi asincroni o iteratori non devono essere "fisse", ma piuttosto "spostabili" se devono essere spostate nei campi della macchina a stati (in modo analogo alle variabili acquisite). Nota che si tratta di un bug preesistente nella specifica indipendente dal resto della proposta, perché i blocchi
unsafeall'interno dei metodiasyncsono sempre stati consentiti. Attualmente c'è un avviso per questo nell'ondata di avvisi C# 12 e renderlo un errore sarebbe un cambiamento che causerebbe un'interruzione.Variabili fisse e mobili: §23.4
In termini precisi, una variabile fissa è una delle seguenti:
- Variabile risultante da un simple_name (§12.8.4) che fa riferimento a una variabile locale, un parametro value, o matrice di parametri, a meno che la variabile non venga acquisita da una funzione anonima (§12.19.6.2) una funzione locale (§13.6.4) o la variabile deve essere spostata come parte di un metodo asincrono (§15.15) o un iteratore (§15.14).
- [...]
Attualmente, nella wave degli avvisi di C# 12, abbiamo un avviso esistente per l'uso di address-of nei metodi asincroni e un errore proposto per l'uso di address-of negli iteratori segnalato per LangVersion 13+ (non è necessario segnalarlo nelle versioni precedenti poiché era impossibile utilizzare codice non sicuro negli iteratori). Possiamo rilassare entrambi questi requisiti per applicarli solo alle variabili effettivamente promosse, non a tutte le variabili locali e ai parametri.
Potrebbe essere possibile usare
fixedper ottenere l'indirizzo di una variabile ospitata o acquisita, anche se il fatto che tali campi sono un dettaglio di implementazione, quindi in altre implementazioni potrebbe non essere possibile usarefixedsu di essi. Si noti che si propone solo di considerare anche le variabili sollevate come "spostabili", ma le variabili acquisite erano già "spostabili" efixednon era consentito per loro.
È possibile consentire
await/yieldall'interno diunsafetranne all'interno di istruzionifixed(il compilatore non può aggiungere variabili attraverso i limiti del metodo). Ciò potrebbe comportare un comportamento imprevisto, ad esempio intorno astackalloc, come descritto nel punto elenco annidato riportato di seguito. Tuttavia, il sollevamento dei puntatori è supportato anche oggi in alcuni scenari (esiste un esempio riportato di seguito in relazione ai puntatori come argomenti), quindi non dovrebbero esserci altre limitazioni nel permetterlo.- È possibile disabilitare la variante non sicura di
stackallocnei metodi asincroni/iteratori, perché il buffer allocato nello stack non persiste attraverso le istruzioniawait/yield. Non si ritiene necessario perché il codice non sicuro per progettazione non impedisce l'uso dopo il libero. Si noti che si potrebbe anche consentire l'uso distackallocin modo non sicuro, a condizione che non venga utilizzato inawait/yield, ma potrebbe essere difficile da analizzare (il puntatore risultante può essere passato in qualsiasi variabile puntatore). In alternativa, potremmo richiedere che siafixednei metodi asincroni/iteratori. Ciò scoraggiare usarlo inawait/yield, ma non corrisponderebbe alla semantica difixedperché l'espressionestackallocnon è un valore spostabile. Si noti che non sarebbe impossibile usare il risultato delstackallocinawait/yieldin modo analogo come è possibile salvare qualsiasi puntatorefixedoggi in un'altra variabile puntatore e usarlo all'esterno del blocco difixed.
- È possibile disabilitare la variante non sicura di
Gli iteratori e i metodi asincroni possono avere parametri puntatore. Avrebbero bisogno di essere sollevati, ma questo non dovrebbe essere un problema in quanto il sollevamento dei puntatori è supportato anche oggi, ad esempio:
unsafe public void* M(void* p) { var d = () => p; return d(); }La proposta mantiene (ed estende/chiarisce) la specifica preesistente che i metodi iteratori iniziano un contesto sicuro anche se si trovano in un contesto non sicuro. Ad esempio, un metodo iteratore non è un contesto non sicuro anche se è definito in una classe con il modificatore
unsafe. In alternativa, è possibile che gli iteratori ereditino il modificatoreunsafecome fanno altri metodi.- Vantaggio: rimuove la complessità dalla specifica e dall'implementazione.
- Vantaggio: allinea gli iteratori ai metodi asincroni (una delle motivazioni della funzionalità).
- Svantaggio: gli iteratori all'interno di classi non sicure non possono contenere istruzioni
yield return, tali iteratori devono essere definiti in una dichiarazione di classe parziale separata senza il modificatoreunsafe. - Svantaggio: si tratta di una modifica radicale in LangVersion=13 (gli iteratori nelle classi non sicure sono consentiti in C# 12).
Anziché un iteratore che definisce un contesto sicuro solo per il corpo, l'intera firma potrebbe essere un contesto sicuro. Ciò non è coerente con il resto del linguaggio in cui i corpi normalmente non influiscono sulle dichiarazioni, ma qui una dichiarazione sarebbe sicura o non sicura a seconda che il corpo sia un iteratore o meno. Si tratterebbe anche di una modifica che causa un'interruzione con LangVersion=13, poiché in C# 12 le firme degli iteratori non sono sicure (ad esempio, possono contenere parametri di array pointer).
Applicazione del modificatore
unsafea un iteratore:- Potrebbe influire sul corpo e sulla firma. Questi iteratori non sarebbero molto utili perché i loro corpi non sicuri non potrebbero contenere
yield returnma soloyield break. - Potrebbe esserci un errore in
LangVersion >= 13come inLangVersion <= 12, perché non è molto utile avere un membro iteratore non sicuro, dato che consente solo di avere parametri di matrice puntatore o setter non sicuri senza un blocco non sicuro aggiuntivo. Ma in futuro potrebbero essere consentiti argomenti puntatore normali.
- Potrebbe influire sul corpo e sulla firma. Questi iteratori non sarebbero molto utili perché i loro corpi non sicuri non potrebbero contenere
Modifica importante di Roslyn:
- È possibile mantenere il comportamento corrente (e anche modificare la specifica in modo che corrisponda) ad esempio introducendo il contesto sicuro nel metodo iteratore, ma ripristinando quindi il contesto non sicuro nella funzione locale.
- Oppure potremmo interrompere tutte le LangVersions, non solo la 13 e le versioni di Lang successive.
- È anche possibile semplificare più drasticamente le regole facendo sì che gli iteratori ereditino il contesto non sicuro, come fanno tutti gli altri metodi. Come discusso in precedenza.
Potrebbe essere eseguito in tutte le versioni linguistiche o solo per
LangVersion >= 13.
Riunioni di progettazione
- 2024-06-03: revisione post-implementazione dello speclet
C# feature specifications