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 příslušných poznámkách ze schůzky jazykového návrhu (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/8652
Shrnutí
Výrazy kolekce představují novou syntaxi terse, [e1, e2, e3, etc]která vytvoří společné hodnoty kolekce. Vložení dalších kolekcí do těchto hodnot je možné pomocí rozprostřeného prvku ..e , jako je například: [e1, ..c2, e2, ..c2].
Můžete vytvořit několik typů podobných kolekcím bez nutnosti externí podpory seznamu BCL. Mezi tyto typy patří:
-
Typy polí, například
int[]. -
Span<T>aReadOnlySpan<T>. - Typy, které podporují inicializátory kolekce, například
List<T>.
Další podpora je k dispozici pro typy podobné kolekci, které nejsou popsané výše prostřednictvím nového atributu a vzoru rozhraní API, které lze přijmout přímo na samotný typ.
Motivation
Hodnoty podobné kolekci se výrazně vyskytují v programování, algoritmech a zejména v ekosystému C#/.NET. Téměř všechny programy budou tyto hodnoty využívat k ukládání dat a odesílání nebo přijímání dat z jiných komponent. V současné době musí téměř všechny programy v jazyce C# používat mnoho různých a bohužel podrobných přístupů k vytváření instancí těchto hodnot. Některé přístupy mají také nevýhody výkonu. Tady je několik běžných příkladů:
- Matice, které vyžadují hodnoty
new Type[]nebonew[]před hodnotami{ ... }. - Rozsahy, které mohou používat
stackalloca další těžkopádné konstrukce. - Inicializátory kolekcí, které vyžadují syntaxi jako
new List<T>(chybí odvození pravděpodobně podrobnéhoT) před jejich hodnotami a které mohou způsobit více relokací paměti, protože používají volání N.Addbez poskytnutí počáteční kapacity. - Neměnné kolekce, které vyžadují syntaxi, jako
ImmutableArray.Create(...)je inicializace hodnot, a které můžou způsobit zprostředkující přidělení a kopírování dat. Efektivnější stavební formy (jakoImmutableArray.CreateBuilder) jsou nepraktické a stále produkují neuložené odpadky.
- Matice, které vyžadují hodnoty
Při pohledu na okolní ekosystém také najdeme příklady všude, kde vytváření seznamů je pohodlnější a příjemnější pro použití. TypeScript, Dart, Swift, Elm, Python a další možnosti se pro tento účel rozhodnou pro stručnou syntaxi s rozšířeným využitím a pro velký efekt. Šetření kurzoru odhalila žádné podstatné problémy vzniklé v těchto ekosystémech s tím, že tyto literály byly sestaveny.
Jazyk C# také přidal vzory seznamů v jazyce C# 11. Tento vzor umožňuje porovnávání a dekonstrukci hodnot podobných seznamu pomocí čisté a intuitivní syntaxe. Na rozdíl od téměř všech ostatních konstruktorů vzorů však tato syntaxe porovnávání/dekonstrukce chybí odpovídající syntaxi konstrukce.
Získání nejlepšího výkonu pro vytváření jednotlivých typů kolekce může být složité. Jednoduchá řešení často ztrácí využití procesoru i paměti. Použití literálového formuláře umožňuje maximální flexibilitu od implementace kompilátoru k optimalizaci literálu tak, aby vznikl alespoň tak dobrý výsledek, jak by mohl uživatel poskytnout, ale s jednoduchým kódem. Velmi často bude kompilátor schopen lépe provádět a cílem specifikace je umožnit implementaci velkých objemů volnosti, pokud jde o strategii implementace, aby to zajistilo.
Pro jazyk C# je potřeba inkluzivní řešení. Měla by splňovat velkou většinu casse pro zákazníky z hlediska typů podobných kolekcí a hodnot, které už mají. Měl by také mít přirozený pocit v jazyce a zrcadlit práci provedenou ve vzorovém párování.
To vede k přirozenému závěru, že syntaxe by měla být podobná [e1, e2, e3, e-etc] nebo , která odpovídá vzorovým ekvivalentům a [p1, ..p2, p3][p1, p2, p3, p-etc] .[e1, ..c2, e2]
Podrobný návrh
Přidají se následující gramatické produkce:
primary_no_array_creation_expression
...
+ | collection_expression
;
+ collection_expression
: '[' ']'
| '[' collection_element ( ',' collection_element )* ']'
;
+ collection_element
: expression_element
| spread_element
;
+ expression_element
: expression
;
+ spread_element
: '..' expression
;
Literály kolekce jsou typu cíle.
Vysvětlení specifikací
Pro stručnost
collection_expressionse bude v následujících částech označovat jako literál.expression_elementinstance se obvykle označují jakoe1,e_natd.spread_elementinstance se obvykle označují jako..s1,..s_natd.typ rozpětí znamená buď
Span<T>neboReadOnlySpan<T>.Literály se obvykle zobrazí tak, aby
[e1, ..s1, e2, ..s2, etc]vyjadřovaly libovolný počet prvků v libovolném pořadí. Důležité je, že tento formulář bude sloužit k reprezentaci všech případů, například:- Prázdné literály
[] - Literály bez nich
expression_element. - Literály bez nich
spread_element. - Literály s libovolným pořadím libovolného typu elementu.
- Prázdné literály
Typ iterace je typ
..s_nproměnné iterace určené jako kdybys_nbyl použit jako výraz, který se iteruje v objektuforeach_statement.Proměnné začínající
__namese používají k vyjádření výsledků vyhodnocenínamehodnoty , uložené v umístění tak, aby se vyhodnocovala pouze jednou. Příkladem__e1je vyhodnocení .e1List<T>,IEnumerable<T>atd. odkazují na příslušné typy vSystem.Collections.Genericoboru názvů.Specifikace definuje překlad literálu do existujících konstruktorů jazyka C#. Podobně jako překlad výrazu dotazu je literál sám sám o sobě legální pouze v případě, že by překlad měl za následek právní kód. Účelem tohoto pravidla je vyhnout se opakování dalších pravidel jazyka, která jsou implicitní (například o převodu výrazů při přiřazení k umístěním úložiště).
Implementace není nutná k překladu literálů přesně tak, jak je uvedeno níže. Jakýkoli překlad je legální, pokud se vytvoří stejný výsledek a neexistují žádné pozorovatelné rozdíly v produkci výsledku.
- Implementace by například mohla překládat literály jako
[1, 2, 3]přímo donew int[] { 1, 2, 3 }výrazu, který sám upeče nezpracovaná data do sestavení, což posune potřebu__indexnebo posloupnost instrukcí pro přiřazení každé hodnoty. Důležité je, že to znamená, že jakýkoli krok překladu může způsobit výjimku za běhu, že stav programu je stále ve stavu označeném překladem.
- Implementace by například mohla překládat literály jako
Odkazy na "přidělení zásobníku" odkazují na jakoukoli strategii přidělení zásobníku, nikoli haldy. Důležité je, že neznamená ani nevyžaduje, aby tato strategie byla prostřednictvím skutečného
stackallocmechanismu. Například použití vložených polí je také povolený a žádoucí přístup k dosažení přidělení zásobníku tam, kde je k dispozici. Všimněte si, že v jazyce C# 12 nelze inicializovat vložená pole pomocí výrazu kolekce. To zůstává otevřeným návrhem.U kolekcí se předpokládá, že se dobře chovají. Například:
- Předpokládá se, že hodnota
Countkolekce vytvoří stejnou hodnotu jako počet prvků při výčtu. - U typů použitých v této specifikaci definované v
System.Collections.Genericoboru názvů se předpokládá, že jsou bez vedlejšího efektu. Kompilátor může například optimalizovat scénáře, ve kterých se takové typy můžou používat jako zprostředkující hodnoty, ale jinak nebudou vystaveny. - Předpokládá se, že volání některého příslušného
.AddRange(x)člena v kolekci bude mít za následek stejnou konečnou hodnotu jako iteracexa přidání všech jeho výčtových hodnot jednotlivě do kolekce s.Add. - Chování literálů kolekce s kolekcemi, které nejsou dobře se chovají, není definováno.
- Předpokládá se, že hodnota
Conversions
Převod výrazu kolekce umožňuje převod výrazu kolekce na typ.
Převod implicitního výrazu kolekce existuje z výrazu kolekce na následující typy:
- Jednorozměrný typ
T[]pole, v takovém případě typ prvkuT -
Typ rozsahu:
System.Span<T>System.ReadOnlySpan<T>
V takovém případě je typ prvkuT
-
Typ s příslušnou metodou create, v takovém případě typ prvku je typ iterace určený z
GetEnumeratormetody instance nebo výčtového rozhraní, nikoli z rozšiřující metody. -
Struktura nebotyp třídy, který implementuje
System.Collections.IEnumerable, kde:Typ mápoužitelný konstruktor, který lze vyvolat bez argumentů a konstruktor je přístupný v umístění výrazu kolekce.
Pokud výraz kolekce obsahuje nějaké prvky, typ má instanci nebo rozšiřující metodu
Add, kde:- Metodu lze vyvolat pomocí argumentu s jednou hodnotou.
- Pokud je metoda obecná, argumenty typu lze odvodit z kolekce a argumentu.
- Metoda je přístupná v umístění výrazu kolekce.
V takovém případě je typ prvkutyp iteracetypu.
-
Typ rozhraní:
System.Collections.Generic.IEnumerable<T>System.Collections.Generic.IReadOnlyCollection<T>System.Collections.Generic.IReadOnlyList<T>System.Collections.Generic.ICollection<T>System.Collections.Generic.IList<T>
V takovém případě je typ prvkuT
Implicitní převod existuje, pokud typ má typT elementu, kde pro každý prvekEᵢ ve výrazu kolekce:
- Pokud
Eᵢje prvek výrazu, existuje implicitní převod zEᵢnaT. - Pokud
Eᵢje rozprostřený prvek..Sᵢ, existuje implicitní převod z typuSᵢiterace naT.
Neexistuje žádný převod výrazu kolekce z výrazu kolekce na multidimenzionální typ pole.
Typy, pro které existuje implicitní převod výrazu kolekce z výrazu kolekce jsou platné cílové typy pro tento výraz kolekce.
Z výrazu kolekce existují následující další implicitní převody:
Na typ
T?hodnoty s možnou hodnotou null, kde existuje převod výrazu kolekce z výrazu kolekce na typThodnoty . Převod je převod výrazu kolekce , zaTnímž následuje implicitní převod nullable zTdoT?.Do typu
Todkazu, kde je k dispozici metoda create ,Tkterá vrací typUa implicitní převod odkazu zUnaT. Převod je převod výrazu kolekce , zaUnímž následuje implicitní odkaz převod zUnaT.Do typu
Irozhraní, kde je k dispozici metoda create ,Ikterá vrací typVa implicitní boxing převodu zVdoI. Převod je převod výrazu kolekce , zaVnímž následuje implicitní boxing převod zVdoI.
Vytvoření metod
Metoda create je označena atributem [CollectionBuilder(...)] pro typ kolekce.
Atribut určuje typ tvůrce a název metody , která má být vyvolána k vytvoření instance typu kolekce.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface,
Inherited = false,
AllowMultiple = false)]
public sealed class CollectionBuilderAttribute : System.Attribute
{
public CollectionBuilderAttribute(Type builderType, string methodName);
public Type BuilderType { get; }
public string MethodName { get; }
}
}
Atribut lze použít na , classstruct, ref structnebo interface.
Atribut není zděděný, i když lze atribut použít na základ class nebo na abstract class.
Typ tvůrce musí být ne generický class nebo struct.
Nejprve je určena sada použitelných metodCM vytvoření.
Skládá se z metod, které splňují následující požadavky:
- Metoda musí mít název zadaný v atributu
[CollectionBuilder(...)]. - Metoda musí být definována přímo u typu tvůrce .
- Metoda musí být
static. - Metoda musí být přístupná tam, kde se používá výraz kolekce.
- Arity metody musí odpovídat arity typu kolekce.
- Metoda musí mít jeden parametr typu
System.ReadOnlySpan<E>, předaný hodnotou. - Existuje převod identity, implicitní převod odkazu nebo boxing převodu z metody návratový typ na typ kolekce.
Metody deklarované na základních typech nebo rozhraních se ignorují a nejsou součástí CM sady.
CM Pokud je sada prázdná, typ kolekce nemá typ elementu a nemá metodu create. Žádný z následujících kroků se nevztahuje.
Pokud pouze jedna metoda z těch v CM sadě má převod identity z E na typ elementutypu kolekce, to je create metoda pro typ kolekce. V opačném případě typ kolekce nemá metodu vytvoření.
Pokud atribut neodkazuje na vyvolání metody s očekávaným podpisem, zobrazí [CollectionBuilder] se chyba.
Pro výraz kolekce s cílovým typemC<S0, S1, …>, kde deklaraceC<T0, T1, …> typu má přidruženou metoduB.M<U0, U1, …>() tvůrce, se na metodu tvůrce použijí argumenty obecného typu z cílového typu v pořadí – a od nejkrajnějšího typu obsahujícího typ nejvíce dovnitř – do metody tvůrce.
Parametr span pro metodu create lze explicitně označit scoped nebo [UnscopedRef]. Pokud je parametr implicitně nebo explicitně scoped, kompilátor může přidělit úložiště pro rozsah v zásobníku, nikoli haldu.
Například možné vytvoření metody pro ImmutableArray<T>:
[CollectionBuilder(typeof(ImmutableArray), "Create")]
public struct ImmutableArray<T> { ... }
public static class ImmutableArray
{
public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items) { ... }
}
Výše uvedenou ImmutableArray<int> ia = [1, 2, 3];metodu create je možné vygenerovat takto:
[InlineArray(3)] struct __InlineArray3<T> { private T _element0; }
Span<int> __tmp = new __InlineArray3<int>();
__tmp[0] = 1;
__tmp[1] = 2;
__tmp[2] = 3;
ImmutableArray<int> ia =
ImmutableArray.Create((ReadOnlySpan<int>)__tmp);
Konstrukce
Prvky výrazu kolekce se vyhodnocují v pořadí, zleva doprava. Každý prvek se vyhodnotí přesně jednou a všechny další odkazy na prvky odkazují na výsledky tohoto počátečního vyhodnocení.
Prvek šíření může být iterated před nebo za následující prvky ve výrazu kolekce jsou vyhodnoceny.
Neošetřená výjimka vyvolaná některou z metod použitých během výstavby se nezachytí a zabrání dalším krokům v konstrukci.
Length, Counta GetEnumerator předpokládá se, že nemají žádné vedlejší účinky.
Pokud cílový typ je struktura nebo typ třídy , který implementuje System.Collections.IEnumerable, a cílový typ nemá metodu create, konstrukce instance kolekce je následující:
Prvky se vyhodnocují v pořadí. Některé nebo všechny prvky mohou být vyhodnoceny v následujících krocích, nikoli dříve.
Kompilátor může určit známou délku výrazu kolekce vyvoláním počítaných vlastností – nebo ekvivalentních vlastností z dobře známých rozhraní nebo typů – u každého výrazu elementu šíření.
Konstruktor, který je použitelný bez argumentů, je vyvolán.
Pro každý prvek v pořadí:
- Pokud je elementem výrazu, je příslušná
Addinstance nebo rozšiřující metoda vyvolána s výrazem elementu jako argument. (Na rozdíl od chování inicializátoru klasické kolekce nemusí být vyhodnocení prvků aAddvolání nutně prokládání.) - Pokud je prvek rozprostřený prvek , použije se jedna z následujících možností:
- Příslušná
GetEnumeratorinstance nebo rozšiřující metoda je vyvolána na výrazu elementu spread a pro každou položku z enumerátoru jeAddpříslušná instance nebo rozšiřující metoda vyvolána v instanci kolekce s položkou jako argument. Pokud enumerátor implementujeIDisposable, bude volánaDisposepo výčtu bez ohledu na výjimky. - V instanci kolekce se jako argument vyvolá příslušná
AddRangeinstance nebo rozšiřující metoda s výrazem elementu spread. - Příslušná
CopyToinstance nebo rozšiřující metoda se vyvolá na výraz elementu spread s instancí kolekce aintindexem jako argumenty.
- Příslušná
- Pokud je elementem výrazu, je příslušná
Během výše uvedených kroků výstavby může být v instanci kolekce jednou nebo vícekrát vyvolána příslušná
EnsureCapacityinstance nebo metoda rozšíření s argumentemintkapacity.
Pokud je cílovým typem pole, rozsah, typ s metodou create nebo rozhraní, je konstrukce instance kolekce následující:
Prvky se vyhodnocují v pořadí. Některé nebo všechny prvky mohou být vyhodnoceny v následujících krocích, nikoli dříve.
Kompilátor může určit známou délku výrazu kolekce vyvoláním počítaných vlastností – nebo ekvivalentních vlastností z dobře známých rozhraní nebo typů – u každého výrazu elementu šíření.
Inicializační instance se vytvoří takto:
- Pokud je cílovým typem pole a výraz kolekce má známou délku, je pole přiděleno s očekávanou délkou.
- Pokud je cílovým typem rozsah nebo typ s metodou vytvoření a kolekce má známou délku, vytvoří se rozsah s očekávanou délkou odkazující na souvislé úložiště.
- V opačném případě se přidělí přechodné úložiště.
Pro každý prvek v pořadí:
- Pokud je elementem výrazu, inicializační instance indexer je vyvolán pro přidání vyhodnoceného výrazu do aktuálního indexu.
- Pokud je prvek rozprostřený prvek , použije se jedna z následujících možností:
- Člen dobře známého rozhraní nebo typu je vyvolán ke kopírování položek z výrazu elementu spread do inicializační instance.
- Příslušná
GetEnumeratorinstance nebo rozšiřující metoda je vyvolána na výraz elementu spread a pro každou položku z enumerátoru je vyvolána inicializační instance indexer pro přidání položky do aktuálního indexu. Pokud enumerátor implementujeIDisposable, bude volánaDisposepo výčtu bez ohledu na výjimky. - Příslušná
CopyToinstance nebo rozšiřující metoda je vyvolána na výraz elementu spread s inicializační instance aintindex jako argumenty.
Pokud bylo pro kolekci přiděleno přechodné úložiště, je instance kolekce přidělena se skutečnou délkou kolekce a hodnoty z inicializační instance se zkopírují do instance kolekce nebo pokud je vyžadováno rozpětí, může kompilátor použít rozsah skutečné délky kolekce z přechodného úložiště. Jinak inicializační instance představuje instanci kolekce.
Pokud má cílový typ metodu create, metoda create se vyvolá s instancí span.
Poznámka: Kompilátor může zpozdit přidávání prvků do kolekce nebo zpožďovat iterace prostřednictvím rozprostřených prvků až po vyhodnocení následných prvků. (Pokud následující rozprostřené elementy mají počítané vlastnosti, které by umožňovaly vypočítat očekávanou délku kolekce před přidělením kolekce.) Kompilátor naopak může do kolekce dychtivě přidávat prvky – a dychtivě iterovat prostřednictvím rozprostřených prvků – pokud není možné zpozdit žádné výhody.
Představte si následující výraz kolekce:
int[] x = [a, ..b, ..c, d];Pokud jsou rozprostřené prvky
bacjsou počítáné, kompilátor by mohl zpozdit přidávání položek zaabaž pocvyhodnocení, aby umožnil přidělení výsledného pole s očekávanou délkou. Potom by kompilátor mohl dychtivě přidávat položky zc, před vyhodnocenímd.var __tmp1 = a; var __tmp2 = b; var __tmp3 = c; var __result = new int[2 + __tmp2.Length + __tmp3.Length]; int __index = 0; __result[__index++] = __tmp1; foreach (var __i in __tmp2) __result[__index++] = __i; foreach (var __i in __tmp3) __result[__index++] = __i; __result[__index++] = d; x = __result;
Prázdný literál kolekce
Prázdný literál
[]nemá žádný typ. Podobně jako u literálu s hodnotou null lze tento literál implicitně převést na jakýkoli typ konstruktovatelné kolekce.Například následující není legální, protože neexistuje žádný cílový typ a nejsou zahrnuty žádné další převody:
var v = []; // illegalŠíření prázdného literálu je povoleno elidovat. Například:
bool b = ... List<int> l = [x, y, .. b ? [1, 2, 3] : []];Pokud
bje hodnota false, není nutné, aby byla pro prázdný výraz kolekce vytvořena žádná hodnota, protože by se okamžitě rozložila do nulových hodnot v posledním literálu.Prázdný výraz kolekce může být singleton, pokud se používá k vytvoření konečné hodnoty kolekce, která je známo, že není proměnlivá. Například:
// Can be a singleton, like Array.Empty<int>() int[] x = []; // Can be a singleton. Allowed to use Array.Empty<int>(), Enumerable.Empty<int>(), // or any other implementation that can not be mutated. IEnumerable<int> y = []; // Must not be a singleton. Value must be allowed to mutate, and should not mutate // other references elsewhere. List<int> z = [];
Bezpečnost ref
Projděte si bezpečné omezení kontextu pro definice hodnot bezpečného kontextu : deklarací blok, člen funkce a kontext volajícího.
Bezpečný kontext výrazu kolekce je:
Bezpečný kontext prázdného výrazu
[]kolekce je kontext volajícího.Pokud je cílovým typem typ
System.ReadOnlySpan<T>rozsahu aTje jedním z primitivních typůbool,sbyte, ,shortbyte,ushort,char,int, ,longulongfloatuintnebodoublea výraz kolekce obsahuje pouze konstantní hodnoty, bezpečný kontext výrazu kolekce je kontext volajícího.Pokud je cílovým typem typ
System.Span<T>rozsahu neboSystem.ReadOnlySpan<T>je bezpečný kontext výrazu kolekce blok deklarace.Pokud je cílovým typem ref typ struktury s metodou create, bezpečný kontext výrazu kolekce je bezpečný kontext vyvolání metody create, kde výraz kolekce je argument span metody.
V opačném případě je bezpečným kontextem výrazu kolekce kontext volajícího.
Výraz kolekce s bezpečným kontextem bloku deklarací nemůže utéct uzavírací obor a kompilátor může uložit kolekci do zásobníku místo haldy.
Chcete-li povolit výraz kolekce pro typ ref struktury řídicí blok deklarace, může být nutné přetypovat výraz na jiný typ.
static ReadOnlySpan<int> AsSpanConstants()
{
return [1, 2, 3]; // ok: span refers to assembly data section
}
static ReadOnlySpan<T> AsSpan2<T>(T x, T y)
{
return [x, y]; // error: span may refer to stack data
}
static ReadOnlySpan<T> AsSpan3<T>(T x, T y, T z)
{
return (T[])[x, y, z]; // ok: span refers to T[] on heap
}
Odvození typu
var a = AsArray([1, 2, 3]); // AsArray<int>(int[])
var b = AsListOfArray([[4, 5], []]); // AsListOfArray<int>(List<int[]>)
static T[] AsArray<T>(T[] arg) => arg;
static List<T[]> AsListOfArray<T>(List<T[]> arg) => arg;
Pravidla odvození typu se aktualizují následujícím způsobem.
Existující pravidla pro první fázi se extrahují do nového oddílu odvozování vstupního typu a pravidlo se přidá do odvozování vstupního typu a odvození výstupního typu pro výrazy výrazů kolekce.
11.6.3.2 První fáze
Pro každý argument metody
Eᵢ:
- Odvození vstupního typu je provedeno z
EᵢodpovídajícíhotypuTᵢparametru.Odvození vstupního typu je provedeno z výrazu
Ena typTnásledujícím způsobem:
- Pokud
Eje výraz kolekce s elementyEᵢaTje typem s typemTₑelementu neboTje typT0?hodnoty null aT0má typTₑelementu, pak pro každýEᵢ:
- Pokud
Eᵢje element výrazu, je odvození vstupního typu provedeno zEᵢdoTₑ.- Pokud
Eᵢje rozprostřený prvek s typemSᵢiterace, je odvození z dolní hranice provedeno zSᵢTₑ.- [existující pravidla z první fáze] ...
11.6.3.7 Odvození výstupního typu
Odvození výstupního
- Pokud
Eje výraz kolekce s elementyEᵢaTje typem s typemTₑelementu neboTje typT0?hodnoty null aT0má typTₑelementu, pak pro každýEᵢ:
- Pokud
Eᵢje element výrazu, je odvození výstupního typu provedeno zEᵢdoTₑ.- Je-li
Eᵢrozprostřený prvek, není odvozována žádná odvození zEᵢ.- [existující pravidla z odvození výstupního typu] ...
Rozšiřující metody
Žádné změny pravidel vyvolání metody rozšíření .
12.8.10.3 Vyvolání metody rozšíření
Metoda
Cᵢ.Mₑrozšíření má nárok , pokud:
- ...
- Implicitní identita, odkaz nebo převod boxování existuje z výrazu na typ prvního parametru .
Mₑ
Výraz kolekce nemá přirozený typ, takže existující převody z typu nelze použít. V důsledku toho nelze výraz kolekce použít přímo jako první parametr pro vyvolání metody rozšíření.
static class Extensions
{
public static ImmutableArray<T> AsImmutableArray<T>(this ImmutableArray<T> arg) => arg;
}
var x = [1].AsImmutableArray(); // error: collection expression has no target type
var y = [2].AsImmutableArray<int>(); // error: ...
var z = Extensions.AsImmutableArray([3]); // ok
Rozlišení přetěžování
Lepší převod z výrazu je aktualizován tak, aby upřednostňoval určité cílové typy v převodech výrazů kolekce.
V aktualizovaných pravidlech:
-
Span_type je jedna z těchto možností:
System.Span<T>-
System.ReadOnlySpan<T>.
-
Array_or_array_interface je jedna z těchto možností:
- typ pole
- jeden z následujících typů rozhraní implementovaných typem pole:
System.Collections.Generic.IEnumerable<T>System.Collections.Generic.IReadOnlyCollection<T>System.Collections.Generic.IReadOnlyList<T>System.Collections.Generic.ICollection<T>System.Collections.Generic.IList<T>
Vzhledem k implicitnímu převodu
C₁, který převádí z výrazuEna typT₁, a implicitnímu převoduC₂, který převádí z výrazuEna typT₂,C₁je lepším převodem nežC₂, pokud platí jedna z následujících možností:
Eje výraz kolekce a jeden z následujících blokování:
T₁isSystem.ReadOnlySpan<E₁>, andT₂isSystem.Span<E₂>, and an implicitní převod is fromE₁to toE₂T₁jeSystem.ReadOnlySpan<E₁>neboSystem.Span<E₁>, aT₂je array_or_array_interface s typemE₂elementu a implicitní převod existuje zE₁doE₂T₁není span_type aT₂nejedná se o span_type a implicitní převod existuje zT₁doT₂Enení výraz kolekce a jeden z následujících blokování:
Epřesně odpovídáT₁aEpřesně neodpovídáT₂Epřesně odpovídá oběma nebo ani jednomu zT₁nichT₂aT₁je lepším cílem převodu nežT₂Eje skupina metod, ...
Příklady rozdílů s překladem přetížení mezi inicializátory polí a výrazy kolekce:
static void Generic<T>(Span<T> value) { }
static void Generic<T>(T[] value) { }
static void SpanDerived(Span<string> value) { }
static void SpanDerived(object[] value) { }
static void ArrayDerived(Span<object> value) { }
static void ArrayDerived(string[] value) { }
// Array initializers
Generic(new[] { "" }); // string[]
SpanDerived(new[] { "" }); // ambiguous
ArrayDerived(new[] { "" }); // string[]
// Collection expressions
Generic([""]); // Span<string>
SpanDerived([""]); // Span<string>
ArrayDerived([""]); // ambiguous
Typy rozpětí
Typy rozpětí ReadOnlySpan<T> a Span<T> oba typy kolekcí jsou konstruktovatelné. Podpora pro ně následuje návrh pro params Span<T>. Konkrétně vytvoření některého z těchto rozsahů způsobí, že pole T[] vytvořené v zásobníku v případě, že pole parametrů spadá do limitů (pokud existuje) nastavených kompilátorem. V opačném případě bude pole přiděleno na haldě.
Pokud se kompilátor rozhodne přidělit v zásobníku, není nutné přeložit literál přímo do konkrétního stackalloc bodu. Například:
foreach (var x in y)
{
Span<int> span = [a, b, c];
// do things with span
}
Kompilátor může přeložit, že pokud stackallocSpan význam zůstane stejný a zachová se bezpečnost rozsahu. Může například přeložit výše uvedené:
Span<int> __buffer = stackalloc int[3];
foreach (var x in y)
{
__buffer[0] = a
__buffer[1] = b
__buffer[2] = c;
Span<int> span = __buffer;
// do things with span
}
Kompilátor může také použít vložená pole, pokud je k dispozici, když se rozhodnete přidělit v zásobníku. Všimněte si, že v jazyce C# 12 nelze inicializovat vložená pole pomocí výrazu kolekce. Tato funkce je otevřený návrh.
Pokud se kompilátor rozhodne přidělit haldu, překlad Span<T> jednoduše:
T[] __array = [...]; // using existing rules
Span<T> __result = __array;
Překlad literálů kolekce
Výraz kolekce má známou délku, pokud je možné spočítat typ kompilace každého rozprostřeného prvku ve výrazu kolekce.
Překlad rozhraní
Překlad nesměnitelného rozhraní
Vzhledem k cílovému typu, který neobsahuje mutační členy, konkrétně IEnumerable<T>IReadOnlyCollection<T>, a IReadOnlyList<T>, je vyžadována vyhovující implementace k vytvoření hodnoty, která implementuje toto rozhraní. Pokud je typ syntetizován, doporučuje se syntetizovaný typ implementovat všechna tato rozhraní, stejně jako ICollection<T> a , bez IList<T>ohledu na typ rozhraní byl cílem. Tím se zajistí maximální kompatibilita se stávajícími knihovnami, včetně těch, které introspektují rozhraní implementovaná hodnotou, aby bylo možné optimalizovat výkon.
Kromě toho musí hodnota implementovat negenerické ICollection a IList rozhraní. Díky tomu mohou výrazy kolekce podporovat dynamické introspekce ve scénářích, jako jsou datové vazby.
Kompatibilní implementace je bezplatná pro:
- Použijte existující typ, který implementuje požadovaná rozhraní.
- Syntetizuje typ, který implementuje požadovaná rozhraní.
V obou případech může použitý typ implementovat větší sadu rozhraní než ty, které jsou přísně požadovány.
Syntetizované typy mohou využívat jakoukoli strategii, kterou chtějí správně implementovat požadovaná rozhraní. Syntetizovaný typ může například vložit prvky přímo uvnitř sebe a vyhnout se nutnosti dalších interních přidělení kolekce. Syntetizovaný typ také nemohl použít žádné úložiště, které by se rozhodlo vypočítat hodnoty přímo. Například vrácení index + 1 pro [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
- Hodnota se musí vrátit
truepři dotazování (ICollection<T>.IsReadOnlypokud je implementováno) a negenerickéIList.IsReadOnlyaIList.IsFixedSize. Tím se zajistí, že uživatelé můžou odpovídajícím způsobem informovat, že kolekce není proměnlivá, i když implementuje proměnlivá zobrazení. - Hodnota musí vyvolat jakékoli volání metody mutaci (například
IList<T>.Add). Tím zajistíte bezpečnost a zabráníte náhodnému ztlumení nesměnitelné kolekce.
Proměnlivý překlad rozhraní
Daný cílový typ, který obsahuje ztlumené členy, konkrétně ICollection<T>IList<T>:
- Hodnota musí být instancí
List<T>.
Překlad známé délky
Díky známé délce lze efektivně vytvořit výsledek s potenciálem pro žádné kopírování dat a bez zbytečného místa pro časovou rezervu ve výsledku.
Pokud nemáte známou délku , nezabrání vytvoření žádného výsledku. Může však vést k dodatečným nákladům na procesor a paměť, které vytvářejí data, a pak přejít do konečného cíle.
U známého literálu
[e1, ..s1, etc]délky začíná překlad nejprve následujícím:int __len = count_of_expression_elements + __s1.Count; ... __s_n.Count;Vzhledem k cílovému typu
Tpro tento literál:Pokud
Tje nějakýT1[], literál se přeloží takto:T1[] __result = new T1[__len]; int __index = 0; __result[__index++] = __e1; foreach (T1 __t in __s1) __result[__index++] = __t; // further assignments of the remaining elementsImplementace může využívat další prostředky k naplnění pole. Například pomocí efektivních metod hromadného kopírování, jako
.CopyTo()je .Pokud
Tje nějakýSpan<T1>, literál se přeloží stejně jako výše, s tím rozdílem, že__resultinicializace se přeloží takto:Span<T1> __result = new T1[__len]; // same assignments as the array translationPřeklad může místo zachování bezpečnosti rozsahu použít
stackalloc T1[]vložené pole nebo vložené polenew T1[].Pokud
Tje nějakýReadOnlySpan<T1>, pak literál je přeložen stejně jako proSpan<T1>případ s tím rozdílem, že konečný výsledek budeSpan<T1>implicitně převeden naReadOnlySpan<T1>.T1KdeReadOnlySpan<T1>je nějaký primitivní typ a všechny prvky kolekce jsou konstantní, nepotřebuje, aby data byla na haldě nebo v zásobníku. Například implementace by mohla vytvořit tento rozsah přímo jako odkaz na část datového segmentu programu.Výše uvedené formuláře (pro pole a rozsahy) jsou základní reprezentací výrazu kolekce a používají se pro následující pravidla překladu:
Pokud
Tje některáC<S0, S1, …>, která má odpovídající metodu create-methodB.M<U0, U1, …>(), literál se přeloží takto:// Collection literal is passed as is as the single B.M<...>(...) argument C<S0, S1, …> __result = B.M<S0, S1, …>([...])Vzhledem k tomu, že metoda create musí mít typ argumentu některé instance
ReadOnlySpan<T>, pravidlo překladu spans platí při předání výrazu kolekce do metody create.Pokud
Tpodporuje inicializátory kolekcí, pak:pokud typ
Tobsahuje přístupný konstruktor s jedním parametremint capacity, literál se přeloží takto:T __result = new T(capacity: __len); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elementsPoznámka: Název parametru musí být
capacity.Tento formulář umožňuje literálu informovat nově vytvořený typ počtu prvků, aby bylo možné efektivně přidělit interní úložiště. Tím se zabrání plýtvání relokacemi při přidání prvků.
jinak se literál přeloží takto:
T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elementsTo umožňuje vytvořit cílový typ, i když bez optimalizace kapacity, aby se zabránilo interní relokaci úložiště.
Neznámý překlad délky
Zadání cílového typu
Tpro literál neznámé délky :Pokud
Tpodporuje inicializátory kolekcí, literál se přeloží takto:T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elementsTo umožňuje šíření libovolného iterovatelného typu, i když s nejmenší možnou optimalizací.
Pokud
Tje nějakýT1[], pak literál má stejnou sémantiku jako:List<T1> __list = [...]; /* initialized using predefined rules */ T1[] __result = __list.ToArray();Výše uvedené je však neefektivní; vytvoří zprostředkující seznam a pak z něj vytvoří kopii konečného pole. Implementace jsou zdarma pro optimalizaci, například vytváření kódu, například takto:
T1[] __result = <private_details>.CreateArray<T1>( count_of_expression_elements); int __index = 0; <private_details>.Add(ref __result, __index++, __e1); foreach (var __t in __s1) <private_details>.Add(ref __result, __index++, __t); // further additions of the remaining elements <private_details>.Resize(ref __result, __index);To umožňuje minimální plýtvání a kopírování bez dalších režijních nákladů, ke kterým může dojít u kolekcí knihoven.
Počet předaných
CreateArraysouborů slouží k poskytnutí nápovědy počáteční velikosti, aby se zabránilo plýtvání změnou velikosti.Pokud
Tje nějaký typ rozsahu, implementace může dodržovat výše uvedenouT[]strategii nebo jakoukoli jinou strategii se stejnou sémantikou, ale lepším výkonem. Například místo přidělení pole jako kopie prvkůCollectionsMarshal.AsSpan(__list)seznamu lze použít k přímému získání hodnoty rozsahu.
Nepodporované scénáře
I když literály kolekce lze použít pro mnoho scénářů, existuje několik, které nejsou schopné nahradit. Patří mezi ně:
- Multidimenzionální pole (např.
new int[5, 10] { ... }). Není k dispozici žádná možnost zahrnout rozměry a všechny literály kolekce jsou pouze lineární nebo mapové struktury. - Kolekce, které předávají svým konstruktorům speciální hodnoty. Není k dispozici žádné zařízení pro přístup k použitému konstruktoru.
- Inicializátory vnořených kolekcí, např.
new Widget { Children = { w1, w2, w3 } }Tato forma musí zůstat, protože má velmi odlišnou sémantiku odChildren = [w1, w2, w3]. Dřívější volání opakovaně,.Add.Childrenzatímco druhá funkce by přiřadil novou kolekci ..ChildrenMohli bychom zvážit, že druhá forma se vrátí k přidání do existující kolekce, pokud.Childrennení možné ji přiřadit, ale zdá se, že by to mohlo být velmi matoucí.
Nejednoznačnosti syntaxe
Existují dvě syntaktické syntaktické nejednoznačnosti, kdy existuje více právních syntaktických interpretací kódu, které používají
collection_literal_expression.Nejednoznačný
spread_elementrange_expressionje s . Technicky vzato může mít:Range[] ranges = [range1, ..e, range2];Pokud chcete tento problém vyřešit, můžeme provést následující:
- Vyžadovat, aby uživatelé závorky
(..e)nebo zahrnuli počáteční index0..e, pokud chtějí rozsah. - Zvolte jinou syntaxi (například
...) pro rozložení. To by bylo nešťastné kvůli nedostatku konzistence se vzory řezů.
- Vyžadovat, aby uživatelé závorky
Existují dva případy, kdy neexistuje pravdivá nejednoznačnost, ale kde syntaxe výrazně zvyšuje parsování složitosti. I když to není problém s daným časem přípravy, zvyšuje se tím kognitivní režie uživatelů při prohlížení kódu.
Nejednoznačnost mezi
collection_literal_expressionpříkazy aattributesmístními funkcemi a příkazy. Uvažovat:[X(), Y, Z()]Může to být jedna z těchto možností:
// A list literal inside some expression statement [X(), Y, Z()].ForEach(() => ...); // The attributes for a statement or local function [X(), Y, Z()] void LocalFunc() { }Bez složitých pohledů by nebylo možné bez použití celého literálu zjistit.
Mezi možnosti, které je potřeba vyřešit, patří:
- Povolte to tím, že provedete analýzu, abyste zjistili, o které z těchto případů se jedná.
- Nepovolte to a vyžadovat, aby uživatel zabalil literál do závorek jako
([X(), Y, Z()]).ForEach(...). - Nejednoznačnost mezi in
collection_literal_expressionaconditional_expressionnull_conditional_operationsa . Uvažovat:
M(x ? [a, b, c]Může to být jedna z těchto možností:
// A ternary conditional picking between two collections M(x ? [a, b, c] : [d, e, f]); // A null conditional safely indexing into 'x': M(x ? [a, b, c]);Bez složitých pohledů by nebylo možné bez použití celého literálu zjistit.
Poznámka: Jedná se o problém i bez přirozeného typu , protože cílový typ se vztahuje na
conditional_expressions.Stejně jako u ostatních bychom mohli vyžadovat, aby závorky byly nejednoznačné. Jinými slovy, předpokládá se
null_conditional_operation, že interpretace není-li napsána takto:x ? ([1, 2, 3]) :. To se ale zdá být docela nešťastné. Tento druh kódu se zdá být nerozumný napsat a bude pravděpodobně cestovat lidi nahoru.
Nevýhody
- To představuje ještě další formu pro výrazy kolekce nad řadu způsobů, jak už máme. Jedná se o větší složitost jazyka. To znamená, že to také umožňuje sjednocení jedné syntaxe
kruhu, aby je všechny pravidlo, což znamená, že stávající základ kódu lze zjednodušit a přesunout na jednotný vzhled všude. - Použití
[... místo{...]}se od syntaxe, která jsme obecně použili pro pole a inicializátory kolekcí, už používáme. Konkrétně používá[...]místo{...}. Nicméně, to bylo již vyřešeno týmem jazyků, když jsme vytvořili seznam vzorů. Pokusili jsme se pracovat{}se vzory seznamů a narazili jsme na nepřesné problémy. Z tohoto důvodu jsme se přestěhovali do[...], což, zatímco nový pro C#, cítí přirozený v mnoha programovacích jazycích a umožnili nám začít znovu bez nejednoznačnosti. Použití[...]jako odpovídající literálová forma doplňuje naše nejnovější rozhodnutí a poskytuje nám čisté místo pro práci bez problému.
To do jazyka zavádí warty. Například následující jsou právní i (naštěstí) znamenají naprosto stejnou věc:
int[] x = { 1, 2, 3 };
int[] x = [ 1, 2, 3 ];
Vzhledem k šířce a konzistenci, kterou přináší nová syntaxe literálu, bychom ale měli zvážit doporučení, aby se lidé přesunuli do nového formuláře. Návrhy a opravy integrovaného vývojového prostředí (IDE) by v tomto ohledu mohly pomoct.
Alternatives
- O jakých dalších návrzích se uvažuje? Jaký je dopad toho, že to neuděláte?
Vyřešené otázky
Má kompilátor použít
stackallocpro přidělení zásobníku, pokud vložená pole nejsou k dispozici a typ iterace je primitivní typ?Řešení: Ne.
stackallocSpráva vyrovnávací paměti vyžaduje větší úsilí nad vloženým polem, aby se zajistilo, že vyrovnávací paměť není přidělena opakovaně, když je výraz kolekce ve smyčce. Další složitost kompilátoru a vygenerovaného kódu převáží výhodu přidělování zásobníku na starších platformách.V jakém pořadí bychom měli vyhodnotit literální prvky ve srovnání s vyhodnocením vlastnosti Length/Count? Měli bychom nejprve vyhodnotit všechny prvky, pak všechny délky? Nebo bychom měli vyhodnotit prvek, pak jeho délku, další prvek atd.?
Řešení: Nejprve vyhodnotíme všechny prvky, pak vše ostatní následuje.
Může literál s neznámou délkou vytvořit typ kolekce, který potřebuje známou délku, například matici, rozsah nebo kolekci Construct(array/span)? To by bylo obtížnější provádět efektivně, ale může to být možné pomocí chytrých použití polí ve fondu a/nebo tvůrce.
Řešení: Ano, umožňujeme vytvořit kolekci oprav a délky z neznámého literálu délky. Kompilátor je povolen tak, aby ho co nejefektivněji implementoval.
Následující text existuje k zaznamenání původní diskuze o tomto tématu.
Uživatelé můžou vždy vytvořit literál neznámé délky do známé délky s kódem takto:
ImmutableArray<int> x = [a, ..unknownLength.ToArray(), b];Je to však nešťastné kvůli nutnosti vynutit přidělení dočasného skladování. Pokud bychom mohli řídit, jak se to vygenerovalo, mohli bychom být efektivnější.
collection_expressionMůže být typ cílený naIEnumerable<T>rozhraní kolekce nebo jiné rozhraní kolekce?Například:
void DoWork(IEnumerable<long> values) { ... } // Needs to produce `longs` not `ints` for this to work. DoWork([1, 2, 3]);Řešení: Ano, literál lze cílit na libovolný typ
I<T>rozhraní, kterýList<T>implementuje. Například:IEnumerable<long>. Jedná se o stejný typ jako typ cílovéhoList<long>rozhraní a následné přiřazení tohoto výsledku k zadanému typu rozhraní. Následující text existuje k zaznamenání původní diskuze o tomto tématu.Otevřená otázka tady určuje, jaký základní typ se má skutečně vytvořit. Jednou z možností je podívat se na návrh .
params IEnumerable<T>Tam bychom vygenerovali pole, které předává hodnoty, podobně jako to, co se stane sparams T[].Může/má kompilátor vygenerovat
Array.Empty<T>()[]? Měli bychom, aby to bylo možné, abychom se vyhnuli přidělení, kdykoli je to možné?Ano. Kompilátor by měl generovat
Array.Empty<T>()pro případ, kdy je to legální a konečný výsledek není proměnlivý. Například cíleníT[],IEnumerable<T>neboIReadOnlyCollection<T>IReadOnlyList<T>. Neměl by se používatArray.Empty<T>, pokud je cíl proměnlivý (ICollection<T>neboIList<T>).Měli bychom rozšířit inicializátory kolekcí, abychom hledali velmi běžnou
AddRangemetodu? Mohl by ho použít základní vytvořený typ k provádění přidávání rozprostřených prvků potenciálně efektivněji. Možná bychom také chtěli hledat věci jako.CopyTo. Mohou zde být nevýhody, protože tyto metody můžou nakonec způsobit nadbytečné přidělení a odesílání a přímé výčet v přeloženého kódu.Ano. Implementace může využívat jiné metody k inicializaci hodnoty kolekce podle předpokladu, že tyto metody mají dobře definovanou sémantiku a že typy kolekcí by se měly "dobře chovat". V praxi by však implementace měla být opatrná, protože výhody jedním způsobem (hromadné kopírování) můžou mít negativní důsledky i (například boxování kolekce struktur).
Implementace by měla využít výhod v případech, kdy neexistují žádné nevýhody. Například s metodou
.AddRange(ReadOnlySpan<T>).
Nevyřešené otázky
- Měli bychom povolit odvození typu elementu , když je typ iterace "nejednoznačný" (podle určité definice)? Například:
Collection x = [1L, 2L];
// error CS1640: foreach statement cannot operate on variables of type 'Collection' because it implements multiple instantiations of 'IEnumerable<T>'; try casting to a specific interface instantiation
foreach (var x in new Collection) { }
static class Builder
{
public Collection Create(ReadOnlySpan<long> items) => throw null;
}
[CollectionBuilder(...)]
class Collection : IEnumerable<int>, IEnumerable<string>
{
IEnumerator<int> IEnumerable<int>.GetEnumerator() => throw null;
IEnumerator<string> IEnumerable<string>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Je vhodné vytvořit a okamžitě indexovat do literálu kolekce? Poznámka: To vyžaduje odpověď na nevyřešenou otázku níže, zda literály kolekce mají přirozený typ.
Přidělení zásobníku pro obrovské kolekce může vyhodit zásobník. Má mít kompilátor heuristiku pro umístění těchto dat do haldy? Měl by být jazyk nezadaný tak, aby umožňoval tuto flexibilitu? Měli bychom postupovat podle specifikace pro
params Span<T>.Potřebujeme cílový typ
spread_element? Představte si například:Span<int> span = [a, ..b ? [c] : [d, e], f];Poznámka: To se může běžně objevit v následující podobě, aby bylo možné podmíněné zahrnutí některých prvků nebo nic, pokud je podmínka nepravda:
Span<int> span = [a, ..b ? [c, d, e] : [], f];Abychom mohli vyhodnotit celý literál, musíme vyhodnotit výrazy prvků uvnitř. To znamená schopnost vyhodnotit
b ? [c] : [d, e]. Chybí však cílový typ pro vyhodnocení tohoto výrazu v kontextu a chybí jakýkoli druh přirozeného typu, nebudeme moct určit, co dělat s tímto[c]nebo[d, e]tímto typem.Abychom to vyřešili, můžeme říci, že při vyhodnocování výrazu literálu
spread_elementbyl implicitní cílový typ ekvivalentní cílovému typu samotného literálu. Ve výše uvedeném příkladu by se tedy přepsal takto:int __e1 = a; Span<int> __s1 = b ? [c] : [d, e]; int __e2 = f; Span<int> __result = stackalloc int[2 + __s1.Length]; int __index = 0; __result[__index++] = a; foreach (int __t in __s1) __result[index++] = __t; __result[__index++] = f; Span<int> span = __result;
Specifikace konstruktovatelného typu kolekce využívající metodu create je citlivá na kontext, při kterém je převod klasifikovaný.
Existence převodu v tomto případě závisí na myšlence typu iteracetypu kolekce. Pokud existuje metoda create , která přebírá místo ReadOnlySpan<T> , kde T je typ iterace, převod existuje. Jinak to nepomůže.
Typ iterace je však citlivý na kontext, při kterém foreach se provádí. Pro stejný typ kolekce se může lišit podle toho, jaké metody rozšíření jsou v oboru, a může být také nedefinováno.
To je v pořádku pro účely foreach , kdy typ není navržen tak, aby byl schopen sám o sobě. Pokud ano, rozšiřující metody nemůžou změnit způsob, jakým je typ přetěžován, bez ohledu na to, co je kontext.
To ale vypadá trochu divně, že převod bude citlivý na kontext, jako je to. Převod je v podstatě "nestabilní". Typ kolekce explicitně navržený tak, aby byl konstruktovatelný, může vynechat definici velmi důležitého detailu – jeho iteračního typu. Ponechte typ "nekonvertible" na sobě.
Zde je příklad:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))]
class MyCollection
{
}
class MyCollectionBuilder
{
public static MyCollection Create(ReadOnlySpan<long> items) => throw null;
public static MyCollection Create(ReadOnlySpan<string> items) => throw null;
}
namespace Ns1
{
static class Ext
{
public static IEnumerator<long> GetEnumerator(this MyCollection x) => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
long s = l;
}
MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
2];
}
}
}
namespace Ns2
{
static class Ext
{
public static IEnumerator<string> GetEnumerator(this MyCollection x) => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l;
}
MyCollection x1 = ["a",
2]; // error CS0029: Cannot implicitly convert type 'int' to 'string'
}
}
}
namespace Ns3
{
class Program
{
static void Main()
{
// error CS1579: foreach statement cannot operate on variables of type 'MyCollection' because 'MyCollection' does not contain a public instance or extension definition for 'GetEnumerator'
foreach (var l in new MyCollection())
{
}
MyCollection x1 = ["a", 2]; // error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type.
}
}
}
Vzhledem k aktuálnímu návrhu, pokud typ nedefinuje samotný typ iterace , kompilátor nemůže spolehlivě ověřit aplikaci atributu CollectionBuilder . Pokud typ iterace neznáme, nevíme, jaký má být podpis metody create . Pokud typ iterace pochází z kontextu, neexistuje žádná záruka, že typ bude vždy použit v podobném kontextu.
Tato funkce kolekce parametrů je také ovlivněna. Zdá se divné, že nelze spolehlivě předpovědět typ params prvku parametru v bodě deklarace. Aktuální návrh také vyžaduje, aby metoda vytvoření byla alespoň tak přístupná jako paramstyp kolekce. Tuto kontrolu nelze provést spolehlivým způsobem, pokud typ kolekce nedefinuje samotný typ iterace .
Všimněte si, že jsme také https://github.com/dotnet/roslyn/issues/69676 otevřeli pro kompilátor, který v podstatě sleduje stejný problém, ale hovoří o něm z hlediska optimalizace.
Návrh
Vyžadovat typ využívající CollectionBuilder atribut definovat jeho iterační typ sám.
Jinými slovy to znamená, že typ by měl buď implementovat IEnumarable/IEnumerable<T>, nebo by měl mít veřejnou GetEnumerator metodu se správným podpisem (to vylučuje všechny rozšiřující metody).
Teď je také nutné , aby byla metoda create přístupná tam, kde se používá výraz kolekce. Toto je další bod závislosti kontextu na základě přístupnosti. Účel této metody je velmi podobný účelu uživatelem definované metody převodu a ten musí být veřejný. Proto bychom měli zvážit, že metoda vytvoření bude také veřejná.
Conclusion
Schváleno s úpravami LDM-2024-01-08
Pojem typu iterace se v průběhu převodů nepoužívá konzistentně.
- Do struktury nebo typu třídy , která implementuje
System.Collections.Generic.IEnumerable<T>:
- Pro každý prvek
Eiexistuje implicitní převod naT.
Zdá se, že se předpokládá, že T je nutné iterační typstruktury nebo typu třídy v tomto případě.
Tento předpoklad je však nesprávný. Což může vést k velmi neobvyklému chování. Například:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(string l) => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l; // Iteration type is string
}
MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
2];
MyCollection x2 = new MyCollection() { "b" };
}
}
- Do struktury nebo typu třídy , který implementuje
System.Collections.IEnumerablea neimplementujeSystem.Collections.Generic.IEnumerable<T>.
Zdá se, že implementace předpokládá, že typ iterace je object, ale specifikace ponechá tento fakt nezadanou a jednoduše nevyžaduje, aby každý prvek převést na nic. Obecně však typ iterace není nutný object . Které lze pozorovat v následujícím příkladu:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable
{
public IEnumerator<string> GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l; // Iteration type is string
}
}
}
Pojem typu iterace je zásadní pro funkci Params Collections . A tento problém vede k podivné nesrovnalosti mezi těmito dvěma funkcemi. Například:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
public void Add(long l) => throw null;
public void Add(string l) => throw null;
}
class Program
{
static void Main()
{
Test("2"); // error CS0029: Cannot implicitly convert type 'string' to 'long'
Test(["2"]); // error CS1503: Argument 1: cannot convert from 'collection expressions' to 'string'
Test(3); // error CS1503: Argument 1: cannot convert from 'int' to 'string'
Test([3]); // Ok
MyCollection x1 = ["2"]; // error CS0029: Cannot implicitly convert type 'string' to 'long'
MyCollection x2 = [3];
}
static void Test(params MyCollection a)
{
}
}
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable
{
IEnumerator IEnumerable.GetEnumerator() => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
public void Add(object l) => throw null;
}
class Program
{
static void Main()
{
Test("2", 3); // error CS1503: Argument 2: cannot convert from 'int' to 'string'
Test(["2", 3]); // Ok
}
static void Test(params MyCollection a)
{
}
}
Pravděpodobně bude dobré zarovnat jeden nebo druhý.
Návrh
Určete převodnost struktury nebo typu třídy, která implementuje System.Collections.Generic.IEnumerable<T>typ iterace nebo System.Collections.IEnumerable z hlediska typu iterace a vyžaduje implicitní převod pro každý prvekEi na typ iterace.
Conclusion
Schváleno LDM-2024-01-08
Má převod výrazů kolekce vyžadovat dostupnost minimální sady rozhraní API pro výstavbu?
Konstruktible typ kolekce podle převodů může být ve skutečnosti nekonstruovatelný, což je pravděpodobné, že vede k nějaké neočekávanému chování řešení přetížení. Například:
class C1
{
public static void M1(string x)
{
}
public static void M1(char[] x)
{
}
void Test()
{
M1(['a', 'b']); // error CS0121: The call is ambiguous between the following methods or properties: 'C1.M1(string)' and 'C1.M1(char[])'
}
}
Nicméně, "C1. M1(řetězec) není kandidátem, který lze použít, protože:
error CS1729: 'string' does not contain a constructor that takes 0 arguments
error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
Tady je další příklad s uživatelem definovaným typem a silnější chybou, která ani nezmíní platného kandidáta:
using System.Collections;
using System.Collections.Generic;
class C1 : IEnumerable<char>
{
public static void M1(C1 x)
{
}
public static void M1(char[] x)
{
}
void Test()
{
M1(['a', 'b']); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
}
public static implicit operator char[](C1 x) => throw null;
IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Vypadá to, že situace je velmi podobná tomu, co jsme použili pro skupinu metod delegování převodů. Tj. existují scénáře, ve kterých existoval převod, ale byl chybný. Rozhodli jsme se to zlepšit tím, že zajistíme, že pokud je převod chybný, pak neexistuje.
Všimněte si, že u funkce "Kolekce parametrů" narazíme na podobný problém. Může být vhodné zakázat použití modifikátoru params pro nekonstruovatelné kolekce. V aktuálním návrhu je však kontrola založená na části převodů . Zde je příklad:
using System.Collections;
using System.Collections.Generic;
class C1 : IEnumerable<char>
{
public static void M1(params C1 x) // It is probably better to report an error about an invalid `params` modifier
{
}
public static void M1(params ushort[] x)
{
}
void Test()
{
M1('a', 'b'); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
M2('a', 'b'); // Ok
}
public static void M2(params ushort[] x)
{
}
IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Zdá se, že problém byl poněkud popsán dříve, viz https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md#collection-expressions. V té době byl proveden argument, že pravidla, jak je uvedeno právě teď, jsou konzistentní s tím, jak jsou specifikovány interpolované obslužné rutiny řetězců. Tady je citát:
Konkrétně byly interpolované obslužné rutiny řetězců původně zadány tímto způsobem, ale po zvážení tohoto problému jsme upravili specifikaci.
I když existuje nějaká podobnost, je tu také důležitý rozdíl, který stojí za zvážení. Tady je citace z https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#interpolated-string-handler-conversion:
Typ
Tje řečeno, že je applicable_interpolated_string_handler_type, pokud je přiřazen .System.Runtime.CompilerServices.InterpolatedStringHandlerAttributeExistuje implicitní interpolated_string_handler_conversion zTinterpolated_string_expression nebo additive_expression složené zcela z _interpolated_string_expression_s a použití pouze+operátorů.
Cílový typ musí mít speciální atribut, který je silným ukazatelem záměru autora, aby byl typ interpolovanou obslužnou rutinou řetězce. Je spravedlivé předpokládat, že přítomnost atributu není náhoda.
Naproti tomu skutečnost, že typ je "enumerable", neznamená, že existuje záměr autora pro typ, který má být konstruktovatelný. Přítomnost metody vytvoření, která je však označena atributem [CollectionBuilder(...)] u typu kolekce, se podobá silnému indikátoru záměru autora pro typ, který má být konstruktovatelný.
Návrh
Pro strukturu nebo typ třídy, který implementuje System.Collections.IEnumerable a který nemá oddíl převodymetody create by měl vyžadovat přítomnost alespoň následujících rozhraní API:
- Přístupný konstruktor, který je použitelný bez argumentů.
- Přístupná
Addinstance nebo metoda rozšíření, kterou lze vyvolat s hodnotou typu iterace jako argumentu.
Pro účely funkce Params Collectons jsou takové typy platné params typy, pokud jsou tato rozhraní API deklarována jako veřejná a jsou to metody instance (vs. rozšíření).
Conclusion
Schváleno s úpravami LDM-2024-01-10
C# feature specifications