Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Obs
Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.
Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningarna (språkkonstruktionsmöte, Language Design Meeting).
Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.
Champion-problem: https://github.com/dotnet/csharplang/issues/1331
Sammanfattning
Förena beteendet mellan iteratorer och asynkrona metoder. Specifikt:
- Tillåt
ref/ref structlokala ochunsafeblock i iteratorer och asynkrona metoder förutsatt att de används i kodsegment utanyieldellerawait. - Varna om
yieldinutilock.
Motivation
Det är inte nödvändigt att förbjuda ref/ref struct lokala variabler och unsafe block i asynkrona/iteratormetoder om de inte används över yield eller await, eftersom de inte behöver höjas.
async void M()
{
await ...;
ref int x = ...; // error previously, proposed to be allowed
x.ToString();
await ...;
// x.ToString(); // still error
}
Brytande ändringar
Det finns inga icke-bakåtkompatibla ändringar i språkspecifikationen, men det finns en icke-bakåtkompatibel ändring i Roslyn-implementeringen (på grund av en specifikationsöverträdelse).
Roslyn bryter mot den del av specifikationen som anger att iteratorer inför en säker kontext (§13.3.1).
Om det till exempel finns en unsafe class med en iteratormetod som innehåller en lokal funktion ärver den lokala funktionen den osäkra kontexten från klassen, även om den borde ha varit i en säker kontext enligt specifikationen på grund av iteratormetoden.
Faktum är att hela iteratormetoden ärvde den osäkra kontexten i Roslyn, det var bara otillåtet att använda osäkra konstruktioner i iteratorer.
I LangVersion >= 13introducerar iteratorer korrekt en säker kontext eftersom vi vill tillåta osäkra konstruktioner i iteratorer.
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
}
}
}
Observera:
- Pausen kan bara bearbetas genom att lägga till
unsafe-modifieraren i den lokala funktionen. - Detta påverkar inte lambdas eftersom de "ärver" "iteratorkontexten" och därför var det omöjligt att använda osäkra konstruktioner inuti dem.
Detaljerad design
Följande ändringar är knutna till LangVersion, d.v.s. C# 12 och lägre kommer att fortsätta att tillåta ref-liknande lokalbefolkningen och unsafe block i asynkrona metoder och iteratorer, och C# 13 lyfter dessa begränsningar enligt beskrivningen nedan.
Specifikationsförtydliganden som matchar den befintliga Roslyn-implementeringen bör dock gälla för alla språkversioner.
Ett block som innehåller en eller flera
yield-instruktioner (§13.15) kallas ett iteratorblock, även om dessayield-instruktioner endast indirekt finns i kapslade block (exklusive kapslade lambdas och lokala funktioner).[...]
Det är ett kompileringsfel för ett iteratorblock som innehåller en osäker kontext (§23.2). Ett iteratorblock definierar alltid en säker kontext, även när dess deklaration är kapslad i en osäker kontext.Iteratorblocket som används för att implementera en iterator (§15.14) definierar alltid en säker kontext, även när iteratordeklarationen är kapslad i ett osäkert sammanhang.
Från den här specifikationen följer den också:
- Om en iteratordeklaration markeras med
unsafe-modifieraren finns signaturen i ett osäkert omfång, men iteratorblocket som används för att implementera iteratorn definierar fortfarande ett säkert omfång. - Den
set-accessorn för en iteratoregenskap eller indexerare (dvs. dessget-accessor implementeras via ett iteratorblock) "ärver" dess säkra/osäkra omfång från deklarationen. - Detta påverkar inte partiella deklarationer utan implementering eftersom de bara är signaturer och inte kan ha en iteratortext.
Observera att det i C# 12 är ett fel att ha en iteratormetod markerad med unsafe-modifieraren, men det tillåts i C# 13 på grund av specifikationsändringen.
Till exempel:
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 */ }
}
}
§13.6.2.4 Ref lokala variabeldeklarationer:
Det är ett kompileringsfel att deklarera en lokal referensvariabel, eller en variabel avDet är ett kompileringsfel att deklarera och använda (även implicit i kompilatorsyntetiserad kod) en referens lokal variabel eller en variabel av enref structtyp, inom en metod som deklarerats med method_modifierasynceller inom en iterator (§15.14).ref structtyp överawaituttryck elleryield return-instruktioner. Mer exakt drivs felet av följande mekanism: efter ettawaituttryck (§12.9.8) eller enyield return-instruktion (§13.15), anses alla ref lokala variabler och variabler av enref structtyp i omfång definitivt vara otilldelade (§9.4).
Observera att det här felet inte nedgraderas till en varning i unsafe kontexter som andra referenssäkerhetsfel.
Det beror på att dessa referensliknande lokala variabler inte kan manipuleras i unsafe-kontexter utan att förlita sig på implementeringsdetaljer om hur omskrivningen av tillståndsmaskinen fungerar. Därför faller det här felet utanför vad vi vill nedgradera till varningar i unsafe-kontexter.
§15.14.1 Iteratorer > Allmänt:
När en funktionsmedlem implementeras med hjälp av ett iteratorblock, är det ett kompileringsfel för den formella parameterlistan för funktionsmedlemmen att specificera några
in,ref readonly,outellerrefparametrar, eller en parameter av typenref struct, eller av en pekartyp.
Ingen ändring i specifikationen krävs för att tillåta unsafe block som inte innehåller awaits i asynkrona metoder, eftersom specifikationen aldrig har otillåtna unsafe block i asynkrona metoder.
Specifikationen bör dock alltid ha förbjudit await inuti unsafe block (den hade redan otillåtit yield i unsafe i §13.3.1 enligt ovan), så vi föreslår följande ändring av specifikationen:
§15.15.1 Asynkrona Funktioner > Allmänt:
Det är ett kompileringsfel för den formella parameterlistan för en asynkron funktion för att ange eventuella
in,outellerrefparametrar eller någon parameter avref structtyp.Det är ett kompileringsfel för ett osäkert sammanhang (§23.2) att innehålla ett
awaituttryck (§12.9.8) eller enyield return-instruktion (§13.15).
Ett kompileringsfel rapporteras för att ta en adress till en lokal eller en parameter i en iterator.
Att ta adressen till en lokal variabel eller en parameter i en asynkron metod är för närvarande en varning i C# 12-varningsvågen.
Observera att fler konstruktionstyper kan fungera eftersom ref tillåts inom segment utan await och yield i asynkrona/iteratormetoder, även om ingen specifikationsändring krävs specifikt för dem eftersom dessa ändringar alla härrör från de ovan nämnda specifikationsändringarna.
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();
}
}
Alternativ
ref/ref structlokala variabler får endast tillåtas i block (§13.3.1) som inte innehållerawait/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 returninutilockkan vara ett fel (somawaitinutilockär) eller en varningsvågsvarning, men det skulle vara en icke-bakåtkompatibel ändring: https://github.com/dotnet/roslyn/issues/72443. Observera att de nyaLock-objektbaseradelock-rapporterna ger kompileringstidsfel föryield returns i sin brödtext, eftersom sådanlock-instruktion motsvarar enusingpå enref structoch inte tillåteryield returns i sin brödtext.Variabler i asynkrona metoder eller iteratormetoder bör inte vara "fasta" utan snarare "flyttbara" om de behöver hissas till fält på tillståndsdatorn (på samma sätt som insamlade variabler). Observera att detta är en befintlig bugg i specifikationen oberoende av resten av förslaget eftersom
unsafeblock inutiasyncmetoder alltid var tillåtna. Det finns för närvarande en varning för detta i C# 12-varningsvågen och att göra det till ett fel skulle vara en icke-bakåtkompatibel ändring.§23.4 Fasta och flyttbara variabler:
I exakta termer är en fast variabel något av följande:
- En variabel som härrör från en simple_name (§12.8.4) som refererar till en lokal variabel, värdeparameter, eller parametermatris, såvida inte variabeln fångas upp av en anonym funktion (§12.19.6.2) eller en lokal funktion (§13.6.4) eller variabeln måste hissas som en del av en asynkron (§15.15) eller en iterator (§15.14) metod.
- [...]
För närvarande har vi en befintlig varning i C# 12-varningskategori för adressering i asynkrona metoder och ett föreslaget fel för adressering i iteratorer som rapporterats för LangVersion 13+ (behöver inte rapporteras i tidigare versioner eftersom det var omöjligt att använda osäker kod i iteratorer). Vi kan lätta på båda dessa för att endast gälla variabler som faktiskt hissas, inte alla lokala variabler och parametrar.
Det kan vara möjligt att använda
fixedför att hämta adressen för en hissad eller infångad variabel, även om det faktum att det är fält är en implementeringsinformation, så i andra implementeringar kanske det inte går att användafixedpå dem. Observera att vi bara föreslår att även hissade variabler ska anses vara "flyttbara", men insamlade variabler var redan "flyttbara" ochfixedtilläts inte för dem.
Vi kan tillåta
await/yieldinutiunsafeförutom ifixed-instruktioner (kompilatorn kan inte fästa variabler över metodgränser). Det kan resultera i ett oväntat beteende, till exempel omkringstackalloc, så som beskrivs i den kapslade punkten nedan. Annars stöds lyftpekare även i dag i vissa scenarier (det finns ett exempel nedan som rör pekare som argument), så det bör inte finnas några andra begränsningar för att tillåta detta.- Vi kan inte tillåta den osäkra varianten av
stackalloci asynkrona/iteratormetoder, eftersom den stackallokerade bufferten inte finns iawait/yield-instruktioner. Det känns inte nödvändigt eftersom osäker kod i designen inte förhindrar "användning efter frigöring". Observera att vi också kan tillåta osäkrastackallocförutsatt att den inte används iawait/yield, men det kan vara svårt att analysera (den resulterande pekaren kan skickas runt i valfri pekarvariabel). Eller så kan vi kräva att det ska varafixedi asynkrona/iteratormetoder. Det skulle avskräcka att använda det överawait/yieldmen skulle inte matcha semantiken ifixedeftersomstackalloc-uttrycket inte är ett flyttbart värde. (Observera att det inte skulle vara omöjligt att användastackallocresultat överawait/yieldpå samma sätt som du kan spara allafixedpekare i dag i en annan pekarvariabel och använda den utanförfixed-blocket.)
- Vi kan inte tillåta den osäkra varianten av
Iterator- och asynkrona metoder kan tillåtas ha pekarparametrar. De skulle behöva hissas, men det bör inte vara ett problem eftersom lyftpekare stöds även i dag, till exempel:
unsafe public void* M(void* p) { var d = () => p; return d(); }Förslaget behåller för närvarande (och utökar/förtydligar) den befintliga specifikationen att iteratormetoder startar en säker kontext även om de befinner sig i ett osäkert sammanhang. En iteratormetod är till exempel inte en osäker kontext även om den definieras i en klass som har
unsafe-modifieraren. Alternativt kan vi få iteratorer att ärvaunsafe-modifieraren som andra metoder gör.- Fördel: tar bort komplexiteten från specifikationen och implementeringen.
- Fördel: anpassar iteratorer med asynkrona metoder (en av drivkrafterna för denna funktion).
- Nackdel: iteratorer i osäkra klasser kunde inte innehålla
yield return-instruktioner, sådana iteratorer måste definieras i en separat partiell klassdeklaration utanunsafemodifieraren. - Nackdel: detta skulle vara en icke-bakåtkompatibel ändring i LangVersion=13 (iteratorer i osäkra klasser tillåts i C# 12).
I stället för en iterator som definierar en säker kontext endast för brödtexten kan hela signaturen vara en säker kontext. Detta är oförenligt med resten av språket i att organ normalt inte påverkar deklarationer, men här skulle en deklaration vara antingen säker eller osäker beroende på om kroppen är en iterator eller inte. Det skulle också vara en icke-bakåtkompatibel ändring i LangVersion=13 eftersom iteratorsignaturerna i C# 12 är osäkra (de kan till exempel innehålla parametrar för pekarmatrisen).
Tillämpa
unsafe-modifieraren på en iterator:- Kan påverka både brödtexten och signaturen. Sådana iteratorer skulle dock inte vara särskilt användbara eftersom deras osäkra sektioner inte kunde innehålla
yield return:er, de kunde bara hayield break:er. - Kan vara ett fel i
LangVersion >= 13som det är iLangVersion <= 12eftersom det inte är särskilt användbart att ha en osäker iteratormedlem eftersom det bara tillåter en att ha pekarmatrisparametrar eller osäkra setters utan ytterligare osäkert block. Men normala pekarargument kan tillåtas i framtiden.
- Kan påverka både brödtexten och signaturen. Sådana iteratorer skulle dock inte vara särskilt användbara eftersom deras osäkra sektioner inte kunde innehålla
Roslyn brytande ändring:
- Vi kan bevara det aktuella beteendet (och till och med ändra specifikationen så att den matchar den) till exempel genom att introducera den säkra kontexten i iteratormetoden men sedan återgå till den osäkra kontexten i den lokala funktionen.
- Eller så kan vi bryta alla LangVersions, inte bara 13 och senare.
- Det är också möjligt att mer drastiskt förenkla reglerna genom att göra iteratorer ärva osäker kontext, precis som alla andra metoder gör. Beskrivs ovan.
Det kan göras i alla språkversioner eller bara för
LangVersion >= 13.
Designa möten
- 2024-06-03: granskning efter implementeringen av specifikationen
C# feature specifications