Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Notitie
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante notities van de Language Design Meeting (LDM) .
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Problemen met kampioen: https://github.com/dotnet/csharplang/issues/1331
Samenvatting
Het gedrag tussen iterators en asynchrone methoden samenvoegen. Specifiek:
- Sta
ref/ref structlokale variabelen enunsafeblokken toe in iterators en asynchrone methoden, op voorwaarde dat ze worden gebruikt in codesegmenten zonderyieldofawait. - Waarschuw voor
yieldbinnenlock.
Motivatie
Het is niet nodig om ref/ref struct lokale variabelen en unsafe blokken in asynchrone/iteratormethoden uit te sluiten als ze niet worden gebruikt in yield of await, omdat ze niet gehesen hoeven te worden.
async void M()
{
await ...;
ref int x = ...; // error previously, proposed to be allowed
x.ToString();
await ...;
// x.ToString(); // still error
}
Belangrijke wijzigingen
Er zijn geen belangrijke wijzigingen in de taalspecificatie, maar er is één belangrijke wijziging in de Roslyn-implementatie (vanwege een schending van de specificaties).
Roslyn schendt het deel van de specificatie dat iterators een veilige context introduceren (§13.3.1).
Als er bijvoorbeeld een unsafe class is met een iteratormethode die een lokale functie bevat, neemt die lokale functie de onveilige context over van de klasse, hoewel deze in een veilige context moet zijn geweest vanwege de iterator-methode.
In feite heeft de hele iteratormethode de onveilige context overgenomen in Roslyn, het was gewoon niet toegestaan om onveilige constructies in iterators te gebruiken.
In LangVersion >= 13zullen iterators een veilige context correct introduceren, omdat we in de toekomst onveilige constructies in iterators willen toestaan.
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
}
}
}
Notitie:
- De onderbreking kan eenvoudig worden omzeild door de
unsafemodifier toe te voegen aan de lokale functie. - Dit heeft geen invloed op lambdas omdat ze de 'iteratorcontext' overnemen en daarom was het onmogelijk om onveilige constructies erin te gebruiken.
Gedetailleerd ontwerp
De volgende wijzigingen zijn gekoppeld aan LangVersion, dat wil zeggen dat C# 12 en lager ref-achtige variabelen en unsafe blokken in asynchrone methoden en iterators blijven verbieden, en dat C# 13 deze beperkingen zal opheffen zoals hieronder beschreven.
Specificatieclarificaties die in lijn zijn met de bestaande Roslyn-implementatie, moeten echter van toepassing zijn op alle LangVersions.
Een blok met een of meer
yieldinstructies (§13.15) wordt een iteratorblok genoemd, zelfs als dezeyieldinstructies slechts indirect zijn opgenomen in geneste blokken (met uitzondering van geneste lambdas en lokale functies).[...]
Het is een compilatiefout voor een iteratorblok dat een onveilige context bevat (§23.2). Een iteratorblok definieert altijd een veilige context, zelfs wanneer de declaratie is genest in een onveilige context.Het iteratorblok dat wordt gebruikt voor het implementeren van een iterator (§15.14) definieert altijd een veilige context, zelfs wanneer de iteratordeclaratie is genest in een onveilige context.
Uit deze specificatie volgt het ook:
- Als een iteratordeclaratie is gemarkeerd met de
unsafemodifier, bevindt de handtekening zich in een onveilig bereik, maar het iteratorblok dat wordt gebruikt om deze iterator te implementeren, definieert nog steeds een veilig bereik. - De
setaccessor van een iterator-eigenschap of indexeerfunctie (d.w.z. degetaccessor wordt geïmplementeerd via een iteratorblok) "erft" zijn veilige/onveilige kader van de declaratie. - Dit heeft geen invloed op gedeeltelijke declaraties zonder implementatie omdat ze alleen handtekeningen zijn en geen iteratorbody kunnen hebben.
In C# 12 is het een fout om een iteratormethode te hebben die is gemarkeerd met de unsafe modifier, maar dat is toegestaan in C# 13 vanwege de wijziging van de specificatie.
Bijvoorbeeld:
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 Verwijzing naar lokale variabele-declaraties:
Het is een compilatiefout om een lokale ref-variabele of een variabele van eenHet is een compileer- en gebruiksfout (zelfs impliciet in door de compiler gegenereerde code) om een lokale ref-variabele of een variabele van eenref structtype te declareren binnen een methode die is gedeclareerd met de method_modifierasync, of binnen een iterator (§15.14).ref structtype overawaitexpressies ofyield returninstructies te verklaren en te gebruiken. Om precies te zijn, wordt de fout veroorzaakt door het volgende mechanisme: na eenawaitexpressie (§12.9.8) of eenyield returninstructie (§13.15) worden alle ref-variabelen en variabelen van eenref structtype in het bereik als definitief niet-toegewezen beschouwd (§9.4).
Houd er rekening mee dat deze fout niet wordt gereduceerd tot een waarschuwing in unsafe-contexten, zoals sommige andere referentieveiligheidsfouten in .
Dat komt doordat deze ref-achtige lokale variabelen niet kunnen worden gemanipuleerd in unsafe contexten zonder te vertrouwen op implementatiedetails van hoe het herschrijven van de toestandsmachine werkt, waardoor deze fout buiten de criteria valt voor wat we willen verlagen naar waarschuwingen in unsafe contexten.
§15.14.1 Iterators > Algemeen:
Wanneer een functielid wordt geïmplementeerd met behulp van een iteratorblok, is het een compilatiefout voor de formele parameterlijst van het functielid om eventuele
in,ref readonly,outofrefparameters, of een parameter van eenref structtype of een aanwijzertypeop te geven.
Er is geen wijziging in de specificatie nodig om unsafe blokken toe te staan die geen awaitin asynchrone methoden bevatten, omdat de specificatie nooit unsafe blokken in asynchrone methoden heeft toegestaan.
De specificatie had echter altijd moeten verbieden dat await binnen unsafe-blokken verschijnt (het had al bepaald dat yield in unsafe in §13.3.1, zoals hierboven vermeld), wij stellen daarom de volgende wijziging voor in de specificatie:
§15.15.1 Asynchrone Functies > Algemeen:
Het is een compilatiefout voor de formele parameterlijst van een asynchrone functie om eventuele
in,outofrefparameters of een parameter van eenref structtype op te geven.Het is een compilatiefout voor een onveilige context (§23.2) om een
await-expressie (§12.9.8) of eenyield returninstructie (§13.15) te bevatten.
§23.6.5 Het adres van de operator:
Er wordt een compilatiefout gerapporteerd voor het nemen van het adres van een lokale variabele of een parameter in een iterator.
Op dit moment wordt het nemen van een adres van een lokale of parameter in een asynchrone methode een waarschuwing in C# 12 waarschuwingsgolf.
Houd er rekening mee dat meer constructies mogelijk zijn dankzij ref dat binnen segmenten is toegestaan zonder await en yield in asynchrone/iterator-methoden, ook al zijn er geen specifieke wijzigingen voor hen nodig in de specificaties, omdat alles voortkomt uit de bovengenoemde specificatiewijzigingen.
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();
}
}
Alternatieven
ref/ref structlokalen kunnen alleen in blokken toegestaan zijn (§13.3.1) die geenawait/yieldbevatten:// 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 returnbinnenlockkan een fout zijn (zoalsawaitbinnenlock) of een waarschuwing voor een waarschuwingsgolf, maar dat zou een ingrijpende wijziging zijn: https://github.com/dotnet/roslyn/issues/72443. Houd er rekening mee dat de nieuweLock-objectgebaseerdelockcompilatiefouten rapporteert vooryield returns in de hoofdtekst, omdat een dergelijkelock-instructie gelijk is aan eenusingop eenref structdieyield returns in de hoofdtekst niet toekent.Variabelen binnen asynchrone of iteratormethoden mogen niet 'vast' zijn, maar eerder 'verplaatsbaar' als ze moeten worden gehesen naar velden van de statusmachine (vergelijkbaar met vastgelegde variabelen). Houd er rekening mee dat dit een bestaande bug in de specificatie is die onafhankelijk is van de rest van het voorstel, omdat
unsafeblokken binnenasyncmethoden altijd zijn toegestaan. Er is momenteel een waarschuwing hiervoor in C# 12-waarschuwingsgolf, en een fout zou een belangrijke wijziging zijn.§23.4 Vaste en verplaatsbare variabelen:
In precieze termen is een vaste variabele een van de volgende:
- Een variabele die voortvloeit uit een simple_name (§12.8.4) die verwijst naar een lokale variabele, waardeparameter, of parametermatrix, tenzij de variabele wordt vastgelegd door een anonieme functie (§12.19.6.2) of een lokale functie (§13.6.4) of de variabele moet worden gehesen als onderdeel van een asynchrone methode (§15.15) of een iterator (§15.14).
- [...]
Momenteel hebben we een bestaande waarschuwing op C# 12 waarschuwingsniveau voor 'address-of' in asynchrone methoden en een voorgestelde fout voor 'address-of' in iterators die is gerapporteerd voor LangVersion 13 en hoger (hoeft niet te worden gerapporteerd in eerdere versies omdat het onmogelijk was om onveilige code in iterators te gebruiken). We zouden beide kunnen versoepelen om alleen van toepassing te zijn op variabelen die daadwerkelijk worden gehesen, niet op alle lokale variabelen en parameters.
Het kan mogelijk zijn om
fixedte gebruiken om het adres van een gehesiste of vastgelegde variabele op te halen, hoewel het feit dat dit velden zijn een implementatiedetails is, zodat het in andere implementaties mogelijk niet mogelijk is omfixederop te gebruiken. Houd er rekening mee dat we alleen overwegen om gelifte variabelen ook als "verplaatsbaar" te beschouwen, maar dat vastgelegde variabelen al "verplaatsbaar" waren en datfixedniet voor hen was toegestaan.
We kunnen
await/yieldbinnenunsafetoestaan, behalve binnenfixed-instructies (compiler kan variabelen niet vastmaken aan de grenzen van de methode). Dit kan leiden tot onverwachte gedrag, bijvoorbeeld in de buurt vanstackalloczoals beschreven in de onderliggende opsomming hieronder. Anders wordt het verplaatsen van pointers zelfs tegenwoordig in sommige scenario's ondersteund (zoals in een voorbeeld hieronder met betrekking tot pointers als argumenten), dus er mogen geen andere beperkingen in het toestaan hiervan zijn.- We zouden de onveilige variant van
stackallocin asynchrone/iteratormethoden kunnen verbieden, omdat de buffer die aan de stack is toegewezen niet blijft bestaan tijdensawait/yieldinstructies. Het voelt niet nodig omdat onveilige code per ontwerp niet "gebruik na gratis" verhindert. Houd er rekening mee dat we ook onveiligestackallockunnen toestaan, mits deze niet wordt gebruikt inawait/yield, maar dat kan lastig zijn om te analyseren (de resulterende aanwijzer kan worden doorgegeven in elke aanwijzervariabele). Ofwel zouden we kunnen vereisen dat hetfixedis in asynchrone/iterator-methoden. Dat zouawait/yieldontmoedigen, maar zou niet overeenkomen met de semantiek vanfixedomdat destackallocexpressie geen verplaatsbare waarde is. (Houd er rekening mee dat het niet onmogelijk is om hetstackallocresultaat inawait/yieldte gebruiken, net zoals u elkefixedaanwijzer vandaag kunt opslaan in een andere aanwijzervariabele en deze buiten hetfixedblok kunt gebruiken.)
- We zouden de onveilige variant van
Iterator- en asynchrone methoden kunnen aanwijzerparameters hebben. Ze moeten geheven worden, maar dat moet geen probleem zijn omdat zelfs vandaag de dag het hijsen van verwijzingen wordt ondersteund, bijvoorbeeld:
unsafe public void* M(void* p) { var d = () => p; return d(); }Het voorstel behoudt (en breidt/verduidelijkt) de reeds bestaande specificatie bij dat iteratormethoden een veilige context beginnen, zelfs als ze zich in een onveilige context bevinden. Een iteratormethode is bijvoorbeeld geen onveilige context, zelfs niet als deze is gedefinieerd in een klasse met de
unsafemodifier. U kunt er ook voor zorgen dat iterators deunsafemodifier overnemen zoals bij andere methoden.- Voordeel: verwijdert complexiteit uit de specificatie en implementatie.
- Voordeel: iterators uitlijnen met asynchrone methoden (een van de motivaties van de functie).
- Nadeel: iterators binnen onveilige klassen kunnen geen
yield returninstructies bevatten, dergelijke iterators moeten worden gedefinieerd in een afzonderlijke gedeeltelijke klassedeclaratie zonder deunsafemodifier. - Nadeel: dit is een belangrijke wijziging in LangVersion=13 (iterators in onveilige klassen zijn toegestaan in C# 12).
In plaats van een iterator die alleen een veilige context voor de hoofdtekst definieert, kan de hele handtekening een veilige context zijn. Dat is niet consistent met de rest van de taal waarin body's normaal gesproken geen invloed hebben op declaraties, maar hier zou een declaratie veilig of onveilig zijn, afhankelijk van of de body een iterator is of niet. Het zou ook een belangrijke wijziging zijn in LangVersion=13, zoals in C# 12 iterator-handtekeningen onveilig zijn (ze kunnen bijvoorbeeld aanwijzermatrixparameters bevatten).
De
unsafewijziging toepassen op een iterator:- Kan zowel de hoofdtekst als de handtekening beïnvloeden. Dergelijke iterators zouden echter niet erg nuttig zijn omdat hun onveilige inhoud geen
yield return's kon bevatten; ze konden alleenyield break's bevatten. - Er zou een fout kunnen zijn in
LangVersion >= 13zoals inLangVersion <= 12, omdat het niet erg nuttig is een onveilig iterator-lid te hebben; dit staat alleen pointer array parameters of onveilige setters toe zonder een extra onveilig blok. Normale aanwijzerargumenten kunnen echter in de toekomst worden toegestaan.
- Kan zowel de hoofdtekst als de handtekening beïnvloeden. Dergelijke iterators zouden echter niet erg nuttig zijn omdat hun onveilige inhoud geen
Roslyn-belangrijke wijziging:
- We kunnen het huidige gedrag behouden (en zelfs de specificatie wijzigen zodat deze overeenkomt) bijvoorbeeld door de veilige context in de iterator-methode te introduceren, maar vervolgens terug te keren naar de onveilige context in de lokale functie.
- Of we kunnen alle LangVersions breken, niet alleen 13 en nieuwer.
- Het is ook mogelijk om de regels drastisch te vereenvoudigen door iterators onveilige context te laten overnemen zoals alle andere methoden doen. Hierboven besproken.
Kan worden uitgevoerd in alle LangVersions of alleen voor
LangVersion >= 13.
Ontwerpvergaderingen
- 2024-06-03: evaluatie na implementatie van de speclet
C# feature specifications