Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Poznámka
Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.
Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v poznámkách ze schůzky o návrhu jazyka (LDM) č. .
Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .
Problém šampiona: https://github.com/dotnet/csharplang/issues/1331
Shrnutí
Sjednoťte chování mezi iterátory a asynchronními metodami. Konkrétně:
- Povolte
ref/ref structmístní prostředí a blokyunsafev iterátorech a asynchronních metodách za předpokladu, že se používají v segmentech kódu bez jakýchkoliyieldneboawait. - Varovat před
yielduvnitřlock.
Motivace
Není nutné zakázat ref/ref struct lokální proměnné a unsafe bloky v asynchronních nebo iterátorových metodách, pokud se nepoužívají napříč yield nebo await, protože není nutné je přesouvat nahoru.
async void M()
{
await ...;
ref int x = ...; // error previously, proposed to be allowed
x.ToString();
await ...;
// x.ToString(); // still error
}
Zásadní změny
Ve specifikaci jazyka nejsou žádné zásadní změny, ale v implementaci Roslyn došlo k jedné zásadní změně (kvůli porušení specifikace).
Roslyn porušuje část specifikace, která uvádí, že iterátory představují bezpečný kontext (§13.3.1).
Například pokud existuje unsafe class s metodou iterátoru, která obsahuje lokální funkci, pak tato lokální funkce dědí nebezpečný kontext z třídy, ačkoli by měl být v bezpečném kontextu vzhledem k metodě iterátoru podle specifikace.
Ve skutečnosti celá metoda iterátoru zdědila nebezpečný kontext v Roslynu, bylo prostě zakázáno používat všechny nebezpečné konstruktory v iterátorech.
V LangVersion >= 13iterátory správně zavádějí bezpečný kontext, protože chceme povolit nebezpečné konstrukce v iterátorech.
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
}
}
}
Poznámka:
- Lze se tomu vyhnout jednoduše přidáním modifikátoru
unsafedo místní funkce. - To nemá vliv na lambda, protože "dědí" kontext "iterátoru", a proto nebylo možné používat nebezpečné konstrukty uvnitř nich.
Podrobný návrh
Následující změny jsou vázány na LangVersion, tj. C# 12 a nižší budou nadále zakázány ref-like locals a unsafe bloky v asynchronních metodách a iterátory a C# 13 tato omezení zruší, jak je popsáno níže.
Ačkoli upřesnění specifikací, která odpovídají stávající implementaci Roslyn, by měla platit ve všech verzích jazyka.
Blok , který obsahuje jeden nebo více příkazů
yield(§13.15) se nazývá blok iterátoru, i když jsou tyto příkazyyieldobsaženy pouze nepřímo v vnořených blocích (s výjimkou vnořených lambda a místních funkcí).[...]
Jedná se o chybu v době kompilace, kdy blok iterátoru obsahuje nebezpečný kontext (§23.2). Blok iterátoru vždy definuje bezpečný kontext, i když je jeho deklarace vnořena do nebezpečného kontextu.Blok iterátoru použitý k implementaci iterátoru (§15.14) vždy definuje bezpečný kontext, i když je deklarace iterátoru vnořená do nebezpečného kontextu.
Z této specifikace také následuje:
- Pokud je deklarace iterátoru označena modifikátorem
unsafe, podpis je v nebezpečném oboru, ale blok iterátoru použitý k implementaci tohoto iterátoru stále definuje bezpečný obor. - Přístupová metoda
setvlastnosti iterátoru nebo indexeru (tj. jehogetpřístupová metoda je implementována prostřednictvím bloku iterátoru) přejímá svůj bezpečný/nebezpečný obor z deklarace. - To nemá vliv na částečné deklarace bez implementace, protože jsou pouze signatury a nemohou mít tělo iterátoru.
Všimněte si, že v jazyce C# 12 se jedná o chybu, že metoda iterátoru je označená modifikátorem unsafe, ale je povolená v jazyce C# 13 kvůli změně specifikace.
Například:
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 Referenční deklarace místních proměnných:
Jedná se o chybu v době kompilace deklarování místní proměnné ref nebo proměnné typuJedná se o chybu v době kompilace, která deklaruje a používá (i implicitně v syntetizovaném kódu kompilátoru) místní proměnnou ref nebo proměnnou typuref structv rámci metody deklarované pomocí method_modifierasync, nebo v rámci iterátoru (§15.14).ref structnapříč výrazyawaitnebo příkazyyield return. Přesněji řečeno, chyba je řízena následujícím mechanismem: za výrazemawait(§12.9.8) neboyield returnpříkazem (§13.15), všechny místní proměnné ref a proměnnéref structtypu v rozsahu jsou považovány za jednoznačně nepřiřazené (§9.4).
Všimněte si, že tato chyba není převedena na upozornění v kontextech unsafe, jako některé jiné chyby zabezpečení ref .
Důvodem je to, že tyto místní hodnoty podobné odkazu nelze manipulovat v kontextu unsafe, aniž byste se museli spoléhat na podrobnosti implementace fungování přepisu stavového počítače, a proto tato chyba spadá mimo hranice toho, co chceme downgradovat na upozornění v unsafe kontextech.
Pokud je člen funkce implementován pomocí bloku iterátoru, jedná se o chybu v době kompilace pro formální seznam parametrů člena funkce k určení libovolného
in,ref readonly,outneborefparametrů nebo parametrref structtypu nebo typu ukazatele.
Není nutná žádná změna specifikace, aby bylo možné povolit unsafe bloků, které neobsahují awaitv asynchronních metodách, protože specifikace nikdy nepovolila unsafe bloků v asynchronních metodách.
Specifikace však měla vždy zakazovat await uvnitř unsafe bloků (již zakázala yield v unsafe v §13.3.1, jak bylo citováno výše), takže navrhujeme následující změnu specifikace:
§15.15.1 Asynchronní funkce > Obecné:
Jedná se o chybu v době kompilace pro formální seznam parametrů asynchronní funkce k určení libovolného
in,outneborefparametrů nebo libovolného parametru typuref struct.Jedná se o chybu při kompilaci, když nebezpečný kontext (§23.2) obsahuje výraz
await(§12.9.8) nebo příkazyield return(§13.15).
Při kompilaci bude hlášena chyba při získání adresy lokální proměnné nebo parametru v iterátoru.
V současné době je pořízení adresy místního nebo parametru v asynchronní metodě upozornění ve vlně upozornění C# 12.
Všimněte si, že díky povolení ref uvnitř segmentů bez await a yield v asynchronních/iterátorových metodách může fungovat více konstrukcí, i když není potřebná žádná specifická změna specifikace pro ně, protože to vše vyplývá z výše uvedených změn specifikace.
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();
}
}
Alternativy
ref/ref structlokály mohou být povoleny pouze v blocích (§13.3.1), které neobsahujíawait/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 returnuvnitřlockmůže být chyba (napříkladawaituvnitřlock) nebo upozornění na vlnovou vlnu, ale to by byla zásadní změna: https://github.com/dotnet/roslyn/issues/72443. Všimněte si, že novýLockobjektovýlockhlásí chyby při kompilaci proyield returnv jeho těle, protože takovýlockpříkaz je ekvivalentníusingnaref struct, který vylučujeyield returnv těle.Proměnné uvnitř asynchronních nebo iterátorových metod by neměly být "pevné", ale "přesunoutelné", pokud je potřeba je přesunout do polí stavového počítače (podobně jako zachycené proměnné). Všimněte si, že se jedná o již existující chybu ve specifikaci nezávisle na zbytku návrhu, protože
unsafebloky uvnitřasyncmetod byly vždy povoleny. V současné době je upozornění pro toto ve varovné vlně C# 12 a učinění z něj chyby by představovalo narušení kompatibility.§23.4 Pevné a pohyblivé proměnné:
Pevná proměnná je přesně jedna z následujících možností:
- Proměnná vyplývající z simple_name (§12.8.4), která odkazuje na místní proměnnou, parametr hodnoty, nebo pole parametrů, pokud proměnná není zachycena anonymní funkcí (§12.19.6.2) nebo místní funkce (§13.6.4) nebo proměnná musí být v rámci asynchronní metody (§15.15) nebo iterátor (§15.14) metody.
- [...]
V současné době máme v rámci vlny varování C# 12 varování týkající se operátoru adresy v asynchronních metodách a navrhovanou chybu pro operátor adresy v iterátorech, hlášenou pro LangVersion 13+ (není nutné ji hlásit v dřívějších verzích, protože v iterátorech nebylo možné používat nebezpečný kód). Mohli bychom uvolnit obě omezení, aby se vztahovala pouze na proměnné, které jsou skutečně zvednuté, nikoli na všechny lokální proměnné a parametry.
Je možné použít
fixedk získání adresy zvýšené nebo zachycené proměnné, i když skutečnost, že jsou to pole v detailech implementace, znamená, že v jiných implementacích nemusí být možné na ně použítfixed. Všimněte si, že navrhujeme zvážit také vyzdvižené proměnné jako „přesouvatelné“, ale zachycené proměnné již byly považovány za „přesouvatelné“ afixedpro ně nebylo povoleno.
Můžeme povolit
await/yielduvnitřunsafes výjimkou příkazůfixed(kompilátor nemůže připnout proměnné přes hranice metody). To může mít za následek neočekávané chování, například kolemstackalloc, jak je popsáno ve vnořeném odrážkovém bodu uvedeném níže. V některých scénářích se podpora natahování ukazatelů používá i dnes (níže je uveden příklad související s ukazateli jako argumenty), takže by neměla existovat žádná další omezení, která by to umožňovala.- Mohli bychom zakázat nebezpečnou variantu
stackallocv asynchronních/iterátorových metodách, protože vyrovnávací paměť alokovaná na zásobníku nepřetrvává přes příkazyawait/yield. Není to nutné, protože nebezpečný kód záměrně nezabrání "použití po volném". Všimněte si, že bychom také mohli povolit nebezpečnýstackallocza předpokladu, že se nepoužívá napříčawait/yield, ale to by mohlo být obtížné analyzovat (výsledný ukazatel může být přenášen v libovolné proměnné ukazatele). Nebo bychom mohli požadovat, abyfixedbylo v asynchronních/iterátorových metodách. To byawait/yieldnedoporučilo používat, ale neodpovídalo by sémanticefixed, protože výrazstackallocnení přemístitelná hodnota. (Všimněte si, že by nebylo možné použít výsledekstackallocnapříčawait/yieldpodobně, jak můžete uložit jakýkoli ukazatelfixeddnes do jiné proměnné ukazatele a použít ho mimo blokfixed.)
- Mohli bychom zakázat nebezpečnou variantu
Iterátor a asynchronní metody můžou mít parametry ukazatele. Musely by být zvednuti, ale nemělo by to být problém, protože zvedání ukazatelů je podporováno i dnes, například:
unsafe public void* M(void* p) { var d = () => p; return d(); }Návrh v současné době zachovává (a rozšiřuje/objasňuje) stávající specifikaci, kterou metody iterátoru začínají bezpečným kontextem, i když jsou v nebezpečném kontextu. Například metoda iterátoru není nebezpečný kontext, i když je definován ve třídě, která má modifikátor
unsafe. Alternativně bychom mohli nechat iterátory „dědit“ modifikátorunsafe, stejně jako to dělají jiné metody.- Výhoda: Eliminuje složitost specifikace a implementace.
- Výhoda: sladí iterátory s asynchronními metodami (to je jedna z motivací této funkce).
- Nevýhodou: iterátory uvnitř nebezpečných tříd nemohly obsahovat
yield returnpříkazy, takové iterátory by musely být definovány v samostatné částečné deklaraci třídy bez modifikátoruunsafe. - Nevýhodou: Jedná se o zásadní změnu v jazyce LangVersion=13 (iterátory v nebezpečných třídách jsou povoleny v jazyce C# 12).
Místo iterátoru, který definuje bezpečný kontext pouze pro tělo, může být celý podpis bezpečným kontextem. To je nekonzistentní se zbytkem jazyka v těchto orgánech, které obvykle neovlivňují deklarace, ale zde by deklarace byla buď bezpečná, nebo nebezpečná v závislosti na tom, zda je tělo iterátorem, nebo ne. Ve verzi LangVersion=13 by došlo také k nekompatibilní změně, protože podpisy iterátoru v C# 12 nejsou bezpečné (například mohou obsahovat parametry pole ukazatelů).
Použití modifikátoru
unsafena iterátor:- Může mít vliv na tělo i podpis. Takové iterátory by však nebyly příliš užitečné, protože jejich nebezpečný kód nemohl obsahovat
yield return, mohly by mít pouzeyield break. - Může být chyba v
LangVersion >= 13stejně jako je vLangVersion <= 12, protože není příliš užitečné mít nebezpečného iterátora jako člena, protože to umožňuje mít pouze parametry pole s ukazateli nebo nebezpečné settery bez dalšího nebezpečného bloku. V budoucnu by ale mohly být povoleny argumenty běžného ukazatele.
- Může mít vliv na tělo i podpis. Takové iterátory by však nebyly příliš užitečné, protože jejich nebezpečný kód nemohl obsahovat
Změna narušující kompatibilitu v Roslynu:
- Mohli bychom zachovat aktuální chování (a dokonce i upravit specifikaci tak, aby odpovídala), například zavedením bezpečného kontextu v metodě iterátoru, ale pak se vrátit k nebezpečnému kontextu v místní funkci.
- Nebo bychom mohli přerušit všechny verze jazyka, nejen 13 a novější.
- Je také možné výrazněji zjednodušit pravidla, tak, že iterátory převezmou nezajištěný kontext stejně jako všechny ostatní metody. Probírané výše.
Může být provedeno ve všech LangVersions nebo jen pro
LangVersion >= 13.
Designérské schůzky
- 2024-06-03: závěrečné vyhodnocení specletu
C# feature specifications