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 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/7700
Shrnutí
V jazyce C# 12 jsme přidali podporu pro vytváření instancí typů kolekcí nad rámec jen polí.
Podívejte se na výrazy kolekce .
Tento návrh rozšiřuje params
podporu na všechny tyto typy kolekcí.
Motivace
Parametr pole params
poskytuje pohodlný způsob volání metody, která přijímá seznam argumentů libovolné délky.
Dnes params
parametr musí být typ pole. Mohlo by být přínosné, aby vývojáři měli stejné pohodlí při volání rozhraní API, která přebírají jiné typy kolekcí. Například ImmutableArray<T>
, ReadOnlySpan<T>
nebo prostý IEnumerable
. Zejména v případech, kdy kompilátor dokáže zabránit implicitní přidělení pole pro účely vytváření kolekce (ImmutableArray<T>
, ReadOnlySpan<T>
atd.).
V dnešní době, v situacích, kdy rozhraní API přebírá typ kolekce, vývojáři obvykle přidávají params
přetížení, které přebírá pole, vývojáři následně vytvoří cílovou kolekci a zavolají původní přetížení s touto kolekcí, což znamená, že uživatelé rozhraní API musí vyměnit pohodlí za další přidělení pole.
Další motivací je možnost přidat přetížení rozsahu parametrů a mít přednost před verzí pole, a to pouze rekompilováním existujícího zdrojového kódu.
Podrobný návrh
Parametry metody
Oddíl parametrů metody se upraví následujícím způsobem.
formal_parameter_list
: fixed_parameters
- | fixed_parameters ',' parameter_array
+ | fixed_parameters ',' parameter_collection
- | parameter_array
+ | parameter_collection
;
-parameter_array
+parameter_collection
- : attributes? 'params' array_type identifier
+ : attributes? 'params' 'scoped'? type identifier
;
parameter_collection se skládá z volitelné sady atributů, params
modifikátoru, volitelného modifikátoru scoped
, typu a identifikátoru . Kolekce parametrů deklaruje jeden parametr daného typu s daným názvem.
Typ kolekce parametrů musí být jedním z následujících platných cílových typů pro výraz kolekce (viz https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions):
- Jednorozměrný typ pole
T[]
, v takovém případě typ prvku jeT
- Rozsah typu
System.Span<T>
System.ReadOnlySpan<T>
ve kterých případech je typ prvkuT
- Typ s odpovídající metodou create, kterou lze vyvolat bez dalších argumentů, což je alespoň tak přístupné jako deklarující člen, a s odpovídajícím typem prvku vyplývajícím z daného určení
- Struktura nebo typ třídy , která implementuje
System.Collections.IEnumerable
kde:Typ má konstruktor, který lze vyvolat bez argumentů a konstruktor je alespoň tak přístupný jako deklarující člen.
Typ má instanční metodu (nikoli rozšíření)
Add
, kde:- Metodu lze vyvolat pomocí argumentu s jednou hodnotou.
- Pokud je metoda obecná, argumenty typu lze odvodit z argumentu.
- Metoda je alespoň tak přístupná jako deklarující člen.
V takovém případě je prvek typu typu iterace typu .
- 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>
ve kterých případech je typ prvkuT
-
Při vyvolání metody kolekce parametrů umožňuje zadat jeden argument daného typu parametru, nebo umožňuje zadat nula nebo více argumentů typu elementu kolekce. Kolekce parametrů jsou podrobněji popsány v kolekcích parametrů .
K parameter_collection může dojít po volitelném parametru, ale nemůže mít výchozí hodnotu – vynechání argumentů pro parameter_collection by místo toho vedlo k vytvoření prázdné kolekce.
Kolekce parametrů
Oddíl pole parametrů je přejmenován a upraven následujícím způsobem.
Parametr deklarovaný s modifikátorem params
je kolekce parametrů. Pokud seznam formálních parametrů obsahuje kolekci parametrů, musí být posledním parametrem v seznamu a musí být typu zadaného v oddílu parametry metody.
Poznámka: Modifikátor
params
není možné kombinovat s modifikátoryin
,out
neboref
. koncová poznámka
Kolekce parametrů umožňuje zadat argumenty jedním ze dvou způsobů volání metody:
- Argument zadaný pro kolekci parametrů může být jediný výraz, který se implicitně konvertibilní na typ kolekce parametrů. V tomto případě kolekce parametrů funguje přesně jako parametr hodnoty.
- Volání může také zadat nula nebo více argumentů pro kolekci parametrů, kde je každý z těchto argumentů výraz, který je implicitně převoditelný na typ elementu kolekce parametrů typu. V tomto případě vyvolání vytvoří instanci typu kolekce parametrů podle pravidel specifikovaných ve výrazech kolekce Collection expressions, jako by argumenty byly použity jako prvky ve výrazu kolekce ve stejném pořadí, a používá nově vytvořenou instanci kolekce jako skutečný argument. Při vytváření instance kolekce se použijí původní nepřevrácené argumenty.
S výjimkou povolení proměnného počtu argumentů ve vyvolání je kolekce parametrů přesně ekvivalentní parametru stejného typu.
Při provádění řešení přetížení může být použitelná metoda s kolekcí parametrů, a to buď v normální podobě, nebo v rozšířené podobě. Rozšířená forma metody je k dispozici pouze v případě, že normální forma metody není použitelná a pouze v případě, že příslušná metoda se stejným podpisem jako rozbalený formulář není již deklarována ve stejném typu.
Potenciální nejednoznačnost vzniká mezi normální formou a rozšířenou formou metody s jedním argumentem kolekce parametrů, pokud ji lze použít jako samotnou kolekci parametrů a zároveň jako prvek kolekce parametrů současně. V případě potřeby nejednoznačnost nepředstavuje žádný problém, protože ji lze vyřešit použitím odlitku nebo výrazu kolekce.
Podpisy a přetížení
Všechna pravidla týkající se modifikátoru params
v Podpisy a přetížení zůstávají nezměněná.
Příslušný člen funkce
Oddíl Příslušný člen funkce se upraví následujícím způsobem.
Pokud člen funkce, který obsahuje kolekci parametrů, není použitelný ve svém normálním formátu, může být místo toho člen funkce použitelný ve svém rozšířeném formuláři:
- Pokud kolekce parametrů není pole, rozbalený formulář není použitelný pro jazykové verze C# 12 a níže.
- Rozšířený formulář je vytvořen nahrazením kolekce parametrů v deklaraci člena funkce jedním nebo více hodnotovými parametry typu prvků kolekce parametrů tak, aby počet argumentů v seznamu argumentů odpovídal celkovému počtu parametrů.
Pokud
A
obsahuje méně argumentů než počet pevných parametrů v deklaraci členu funkce, nelze zvětšovanou formu členu funkce vytvořit, a proto ji nelze použít. - V opačném případě je rozbalený formulář použitelný, pokud pro každý argument v
A
, platí jedna z následujících hodnot:- režim předávání parametrů argumentu je shodný s režimem předávání parametrů odpovídajícího parametru a
- pro parametr s pevnou hodnotou nebo parametr hodnoty vytvořený rozšířením existuje implicitní převod z výrazu argumentu na typ odpovídajícího parametru nebo
- pro parametr
in
,out
neboref
je typ výrazu argumentu shodný s typem odpovídajícího parametru.
- režim předávání parametrů argumentu je hodnota a režim předávání parametrů odpovídajícího parametru je vstup a implicitní převod existuje z výrazu argumentu na typ odpovídajícího parametru.
- režim předávání parametrů argumentu je shodný s režimem předávání parametrů odpovídajícího parametru a
Lepší člen funkce
Oddíl Better function member se upraví následujícím způsobem.
Při seznamu argumentů A
se sadou výrazů argumentů {E₁, E₂, ..., Eᵥ}
a dvěma použitelnými členy funkce Mᵥ
a Mₓ
s typy parametrů {P₁, P₂, ..., Pᵥ}
a {Q₁, Q₂, ..., Qᵥ}
je Mᵥ
definován jako lepší člen funkce než Mₓ
, pokud
- pro každý argument není implicitní převod z
Eᵥ
naQᵥ
lepší než implicitní převod zEᵥ
naPᵥ
a - pro alespoň jeden argument je převod z
Eᵥ
naPᵥ
lepší než převod zEᵥ
naQᵥ
.
Pokud jsou sekvence typu parametru {P₁, P₂, ..., Pᵥ}
a {Q₁, Q₂, ..., Qᵥ}
ekvivalentní (tj. každá Pᵢ
má převod identity na odpovídající Qᵢ
), použijí se následující pravidla pro rozlišení, aby bylo možné určit člena lepší funkce.
- Pokud
Mᵢ
je ne generická metoda aMₑ
je obecná metoda,Mᵢ
je lepší nežMₑ
. - V opačném případě, pokud
Mᵢ
je použitelná v normální podobě aMₑ
má kolekci parametrů a je použitelná pouze v rozšířené podobě, pakMᵢ
je lepší nežMₑ
. - V opačném případě, pokud obě metody mají kolekce parametrů a jsou použitelné pouze v jejich rozbalených formách, a pokud params kolekce
Mᵢ
má méně prvků než kolekce parametrůMₑ
, pakMᵢ
je lepší nežMₑ
. - V opačném případě, pokud
Mᵥ
má konkrétnější typy parametrů nežMₓ
,Mᵥ
je lepší nežMₓ
. Nechť{R1, R2, ..., Rn}
a{S1, S2, ..., Sn}
představují nepotřebné a nevyexpandované typy parametrůMᵥ
aMₓ
. Typy parametrůMᵥ
jsou konkrétnější než uMₓ
, pokud platí, že pro každý parametr neníRx
méně specifický nežSx
, a přitom alespoň u jednoho parametru jeRx
konkrétnější nežSx
:- Parametr typu je méně specifický než parametr bez typu.
- Rekurzivně je konstruovaný typ konkrétnější než jiný konstruovaný typ (se stejným počtem argumentů typu), pokud je alespoň jeden argument typu konkrétnější a argument typu není menší než odpovídající argument typu v druhém.
- Typ pole je konkrétnější než jiný typ pole (se stejným počtem dimenzí), pokud je typ prvku prvního konkrétnější než typ prvku druhého.
- V opačném případě, pokud je jeden člen nezdviženým operátorem a druhý je zdvižený operátor, je lepší nezdvižený operátor.
- Pokud nebyl nalezen žádný člen funkce, aby byl lepší a všechny parametry
Mᵥ
mají odpovídající argument, zatímco výchozí argumenty musí být nahrazeny alespoň jedním volitelným parametrem vMₓ
, je lepšíMᵥ
nežMₓ
. - Pokud alespoň pro jeden parametr
Mᵥ
používá lepší způsob předávání parametrů (§12.6.4.4) než odpovídající parametr vMₓ
a žádný z parametrů vMₓ
nepoužívá lepší způsob předávání parametrů nežMᵥ
, pak jeMᵥ
lepší nežMₓ
. -
Jinak platí, že pokud obě metody mají kolekce parametrů a jsou použitelné pouze v rozšířených formách,
Mᵢ
je lepší nežMₑ
pokud stejná sada argumentů odpovídá prvkům kolekce pro obě metody, a jedna z následujících podmínek platí (to odpovídá https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md):-
Obě kolekce parametrů nejsou typu a existuje implicitní převod z kolekce parametrů
Mᵢ
na kolekci parametrůMₑ
-
kolekce parametrů
Mᵢ
jeSystem.ReadOnlySpan<Eᵢ>
a kolekce parametrůMₑ
jeSystem.Span<Eₑ>
a převod identity existuje zEᵢ
naEₑ
-
kolekce parametrů
Mᵢ
jeSystem.ReadOnlySpan<Eᵢ>
neboSystem.Span<Eᵢ>
a kolekce parametrůMₑ
je array_or_array_interface__type s prvkem typuEₑ
, a existuje identická konverze zEᵢ
naEₑ
-
Obě kolekce parametrů nejsou typu a existuje implicitní převod z kolekce parametrů
- Jinak není žádný člen funkce lepší.
Důvod, proč je nové pravidlo rozstřelu umístěno na konci seznamu, je poslední podpoložka.
- Obě kolekce parametrů nejsou typu a existuje implicitní převod z kolekce parametrů
Mᵢ
na kolekci parametrůMₑ
Platí pro pole a proto dřívější rozhodnutí při určování pořadí způsobí změnu chování ve stávajících scénářích.
Například:
class Program
{
static void Main()
{
Test(1);
}
static void Test(in int x, params C2[] y) {} // There is an implicit conversion from `C2[]` to `C1[]`
static void Test(int x, params C1[] y) {} // Better candidate because of "better parameter-passing choice"
}
class C1 {}
class C2 : C1 {}
Pokud platí některá z předchozích pravidel pro rozvázání nerozhodností (včetně pravidla lepších konverzí argumentů), výsledek rozlišení přetížení se může lišit ve srovnání s případem, kdy se jako argument použije explicitní výraz kolekce.
Například:
class Program
{
static void Test1()
{
M1(['1', '2', '3']); // IEnumerable<char> overload is used because `char` is an exact match
M1('1', '2', '3'); // IEnumerable<char> overload is used because `char` is an exact match
}
static void M1(params IEnumerable<char> value) {}
static void M1(params System.ReadOnlySpan<MyChar> value) {}
class MyChar
{
private readonly int _i;
public MyChar(int i) { _i = i; }
public static implicit operator MyChar(int i) => new MyChar(i);
public static implicit operator char(MyChar c) => (char)c._i;
}
static void Test2()
{
M2([1]); // Span overload is used
M2(1); // Array overload is used, not generic
}
static void M2<T>(params System.Span<T> y){}
static void M2(params int[] y){}
static void Test3()
{
M3("3", ["4"]); // Ambiguity, better-ness of argument conversions goes in opposite directions.
M3("3", "4"); // Ambiguity, better-ness of argument conversions goes in opposite directions.
// Since parameter types are different ("object, string" vs. "string, object"), tie-breaking rules do not apply
}
static void M3(object x, params string[] y) {}
static void M3(string x, params Span<object> y) {}
}
Naším hlavním zájmem jsou však scénáře, kdy se přetížení liší pouze podle typu kolekce parametrů, ale typy kolekcí mají stejný typ elementu. Chování by mělo být v souladu s explicitními výrazy sbírky v těchto případech.
"Podmínka 'pokud stejná sada argumentů odpovídá prvkům kolekce pro obě metody' je důležitá pro situace, jako například:"
class Program
{
static void Main()
{
Test(x: 1, y: 2); // Ambiguous
}
static void Test(int x, params System.ReadOnlySpan<int> y) {}
static void Test(int y, params System.Span<int> x) {}
}
Není rozumné porovnávat kolekce vytvořené z různých prvků.
Tato část byla zkontrolována na LDM a byla schválena.
Jedním z účinků těchto pravidel je, že když je dostupných params
různých typů elementů, při zavolání s prázdným seznamem argumentů budou nejednoznačné.
Například:
class Program
{
static void Main()
{
// Old scenarios
C.M1(); // Ambiguous since params arrays were introduced
C.M1([]); // Ambiguous since params arrays were introduced
// New scenarios
C.M2(); // Ambiguous in C# 13
C.M2([]); // Ambiguous in C# 13
C.M3(); // Ambiguous in C# 13
C.M3([]); // Ambiguous in C# 13
}
public static void M1(params int[] a) {
}
public static void M1(params int?[] a) {
}
public static void M2(params ReadOnlySpan<int> a) {
}
public static void M2(params Span<int?> a) {
}
public static void M3(params ReadOnlySpan<int> a) {
}
public static void M3(params ReadOnlySpan<int?> a) {
}
}
Vzhledem k tomu, že upřednostňujeme typ prvku před všemi ostatními, to se zdá rozumné; jazyku nic neřekne, jestli by uživatel v tomto scénáři raději int?
než int
.
Dynamická vazba
Rozšířené verze kandidátů, které využívají kolekce parametrů bez pole, nebudou považovány za platné kandidáty podle aktuálního vazebníku modulu runtime jazyka C#.
Pokud primary_expression nemá typ kompilace dynamic
, provede vyvolání metody omezenou kontrolu doby kompilace, jak je popsáno v §12.6.5 Kontrola doby kompilace dynamického člena vyvolání.
Pokud test projde pouze jeden kandidát, vyvolání kandidáta je staticky vázané, pokud jsou splněny všechny následující podmínky:
- kandidát je lokální funkce.
- kandidát buď není obecný, nebo jsou explicitně zadány argumenty jeho typu;
- neexistuje žádná nejednoznačnost mezi normálními a rozšířenými formami kandidáta, které nelze v době kompilace vyřešit.
V opačném případě je invocation_expression dynamicky svázaný.
Pokud výše uvedený test prošel pouze jeden kandidát:
- pokud je tento kandidát místní funkcí, dojde k chybě v době kompilace;
- Pokud je tento kandidát použitelný pouze v rozšířené podobě využívající kolekce parametrů bez pole, dojde k chybě v době kompilace.
Měli bychom také zvážit vrácení/opravu porušení specifikace, které má vliv na místní funkce dnes, viz https://github.com/dotnet/roslyn/issues/71399.
LDM potvrdili, že chceme toto porušení specifikace opravit.
Stromy výrazů
Výrazy kolekce nejsou podporovány ve stromech výrazů. Podobně rozšířené formy kolekcí parametrů bez pole nebudou podporovány ve stromech výrazů. Nebudeme měnit způsob, jakým kompilátor vytváří vazby lambda pro stromy výrazů s cílem zabránit použití rozhraní API využívajících rozšířené formy kolekcí parametrů bez pole.
Pořadí vyhodnocení s kolekcemi bez pole v jiných než triviálních scénářích
Tato část byla zkontrolována na LDM a byla schválena. Navzdory skutečnosti, že se případy pole odchylují od jiných kolekcí, nemusí oficiální specifikace jazyka určovat různá pravidla pro pole. Odchylky lze jednoduše považovat za artefakt implementace. Současně nemáme v úmyslu měnit stávající chování týkající se polí.
Pojmenované argumenty
Instance kolekce se vytvoří a naplní po vyhodnocení lexicky předchozího argumentu, ale před vyhodnocením lexicky následujícího argumentu.
Například:
class Program
{
static void Main()
{
Test(b: GetB(), c: GetC(), a: GetA());
}
static void Test(int a, int b, params MyCollection c) {}
static int GetA() => 0;
static int GetB() => 0;
static int GetC() => 0;
}
Pořadí vyhodnocení je následující:
-
GetB
se nazývá -
MyCollection
je vytvořeno a naplněno,GetC
se volá v procesu. -
GetA
se nazývá -
Test
se nazývá
Všimněte si, že v případě pole parametrů je pole vytvořeno těsně před vyvoláním cílové metody, po vyhodnocení všech argumentů v jejich lexikálním pořadí.
Složené přiřazení
Instance kolekce se vytvoří a naplní po vyhodnocení lexicky předchozího indexu, ale před vyhodnocením lexicky následujícího indexu. Instance se používá pro vyvolání getterů a setterů cílového indexeru.
Například:
class Program
{
static void Test(Program p)
{
p[GetA(), GetC()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
static int GetC() => 0;
}
Pořadí vyhodnocení je následující:
-
GetA
je volán a ukládán do mezipaměti -
MyCollection
je vytvořen, naplněn a ukládán do mezipaměti,GetC
se v procesu volá. - Getter indexeru je spuštěn s hodnotami uloženými v mezipaměti pro indexy.
- Výsledek je zvýšen.
- Funkce setter indexeru se vyvolá s hodnotami uloženými v mezipaměti pro indexy a výsledkem přírůstku.
Příklad s prázdnou kolekcí:
class Program
{
static void Test(Program p)
{
p[GetA()]++;
}
int this[int a, params MyCollection c] { get => 0; set {} }
static int GetA() => 0;
}
Pořadí vyhodnocení je následující:
-
GetA
je volána a ukládána do mezipaměti - Je vytvořen prázdný
MyCollection
a uložen do mezipaměti. - Getter indexeru je spuštěn s hodnotami uloženými v mezipaměti pro indexy.
- Výsledek je zvýšen.
- Funkce setter indexeru se vyvolá s hodnotami uloženými v mezipaměti pro indexy a výsledkem přírůstku.
Inicializátor objektů
Instance kolekce se vytvoří a naplní po vyhodnocení lexicky předchozího indexu, ale před vyhodnocením lexicky následujícího indexu. Instance se používá k vyvolání getteru indexeru tolikrát, kolikrát je to případně nutné.
Například:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA(), GetC()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetC() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
Pořadí vyhodnocení je následující:
-
GetA
je voláno a ukládáno do mezipaměti -
MyCollection
je vytvořen, naplněn a ukládán do mezipaměti,GetC
se v procesu volá. - Getter indexeru je spuštěn s hodnotami uloženými v mezipaměti pro indexy.
-
GetF1
se vyhodnotí a přiřadí se k poliF1
uC1
, který byl vrácen v předchozím kroku. - Getter indexeru je spuštěn s hodnotami uloženými v mezipaměti pro indexy.
-
GetF2
se vyhodnotí a přiřadí k poliF2
objektuC1
, který byl vrácen v předchozím kroku.
Všimněte si, že v případě pole parametrů se jeho prvky vyhodnocují a ukládají do mezipaměti, ale pro každé vyvolání getteru indexeru se používá nová instance pole (se stejnými hodnotami uvnitř). V předchozím příkladu je pořadí vyhodnocení následující:
-
GetA
je volán a uložen do mezipaměti -
GetC
je volán a ukládán do mezipaměti - Getter indexeru je vyvolán s mezipamětí obsahující
GetA
a novým polem naplněným uloženýmGetC
-
GetF1
se vyhodnotí a přiřadí se do poleF1
C1
, které bylo vráceno v předchozím kroku. - Getter indexeru je vyvolán s mezipamětí obsahující
GetA
a novým polem naplněným uloženýmGetC
-
GetF2
se vyhodnotí a přiřadí k poliF2
objektuC1
vrácenému v předchozím kroku.
Příklad s prázdnou kolekcí:
class C1
{
public int F1;
public int F2;
}
class Program
{
static void Test()
{
_ = new Program() { [GetA()] = { F1 = GetF1(), F2 = GetF2() } };
}
C1 this[int a, params MyCollection c] => new C1();
static int GetA() => 0;
static int GetF1() => 0;
static int GetF2() => 0;
}
Pořadí vyhodnocení je následující:
-
GetA
se vyvolává a ukládá do mezipaměti - Je vytvořen prázdný
MyCollection
a uložen do mezipaměti. - Getter indexeru je spuštěn s hodnotami uloženými v mezipaměti pro indexy.
-
GetF1
se vyhodnotí a přiřadí k poliF1
, které je součástíC1
, vráceného v předchozím kroku. - Getter indexeru je spuštěn s hodnotami uloženými v mezipaměti pro indexy.
-
GetF2
je vyhodnoceno a přiřazeno k poliF2
vrácenému v předchozím kroku.
Bezpečnost referencí
Sekce výrazy kolekce s bezpečností ref se vztahuje na konstrukci kolekcí parametrů při vyvolání rozhraní API v jejich rozšířené formě.
Parametry parametrů jsou implicitně scoped
, pokud je jejich typ ref strukturou. K přepsání lze použít unscopedRefAttribute.
Metadata
V metadatech bychom mohli označit parametry bez pole params
pomocí System.ParamArrayAttribute
, protože pole params
jsou dnes označena.
Zdá se ale, že pro parametry, které nejsou params
pole, budeme mnohem bezpečnější používat jiný atribut.
Například aktuální kompilátor VB nebude schopen zpracovat je označené ParamArrayAttribute
, ať už v normální, nebo v rozšířené podobě. Proto je pravděpodobné, že přidání modifikátoru "params" naruší uživatele VB a velmi pravděpodobně ovlivní uživatele z jiných jazyků nebo nástrojů.
Vzhledem k tomu, že parametry, které nejsou pole params
, jsou označeny novým System.Runtime.CompilerServices.ParamCollectionAttribute
.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public sealed class ParamCollectionAttribute : Attribute
{
public ParamCollectionAttribute() { }
}
}
Tato část byla zkontrolována na LDM a byla schválena.
Otevřené otázky
Alokace zásobníku
Tady je citace z https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions: "Alokace zásobníku pro obrovské kolekce mohou přetížit 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>
." Zní to, že musíme odpovědět na otázky v kontextu tohoto návrhu.
[Vyřešeno] Implicitně scoped
parametry
Bylo navrženo, aby, když params
mění parametr ref struct
, měl být považován za deklarovaný scoped
.
Tvrdí se, že počet případů, kdy chcete, aby byl parametr omezen, je při procházení případů BCL prakticky vždy 100%. V několika případech, které to potřebují, může být výchozí hodnota přepsána pomocí [UnscopedRef]
.
Může však být nežádoucí změnit výchozí nastavení jednoduše na základě přítomnosti modifikátoru params
. Zejména, že u scénářů s přepisováním/implementací nemusí modifikátor params
odpovídat.
Usnesení:
Parametry parametrů jsou implicitně vymezeny – https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements.
[Vyřešeno] Zvažte prosazení scoped
nebo params
napříč úpravami.
Dříve jsme uvedli, že parametry params
by měly být ve výchozím nastavení scoped
. To však představuje neobvyklé chování při předefinování, kvůli našim stávajícím pravidlům ohledně nového vyjádření params
.
class Base
{
internal virtual Span<int> M1(scoped Span<int> s1, params Span<int> s2) => throw null!;
}
class Derived : Base
{
internal override Span<int> M1(Span<int> s1, // Error, missing `scoped` on override
Span<int> s2 // Proposal: Error: parameter must include either `params` or `scoped`
) => throw null!;
}
Máme rozdíl v chování mezi přenosem params
a přenosem scoped
napříč přepsáními zde: params
je zděděno implicitně a s ním i scoped
, zatímco scoped
sama o sobě je není zděděno implicitně a musí se opakovat na všech úrovních.
Návrh: Měli bychom vynutit, aby přepsání parametrů params
explicitně uvedlo params
nebo scoped
, pokud je původní definice parametrem scoped
. Jinými slovy, s2
v Derived
musí mít params
, scoped
nebo obojí.
Usnesení:
Budeme vyžadovat explicitní uvedení scoped
nebo params
při přepsání parametru params
, když by bylo vyžadováno použití jiného parametru nežparams
– https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides.
[Vyřešeno] Má přítomnost požadovaných členů zabránit deklaraci params
parametru?
Podívejte se na následující příklad:
using System.Collections;
using System.Collections.Generic;
public class MyCollection1 : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(long l) => throw null;
public required int F; // Collection has required member and constructor doesn't initialize it explicitly
}
class Program
{
static void Main()
{
Test(2, 3); // error CS9035: Required member 'MyCollection1.F' must be set in the object initializer or attribute constructor.
}
// Proposal: An error is reported for the parameter indicating that the constructor that is required
// to be available doesn't initialize required members. In other words, one is able
// to declare such a parameter under the specified conditions.
static void Test(params MyCollection1 a)
{
}
}
Usnesení:
Ověříme členy required
pomocí konstruktoru, který se používá pro určení způsobilosti být parametrem params
v místě deklarace - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters.
Alternativy
Existuje alternativní návrh, který rozšiřuje params
pouze pro ReadOnlySpan<T>
.
Dalo by se také říci, že s výrazy kolekce nyní v jazyce není vůbec nutné rozšiřovat podporu params
. Pro jakýkoli typ kolekce. Aby mohl vývojář využívat rozhraní API s typem kolekce, stačí přidat dva znaky, [
před rozbalený seznam argumentů a ]
za něj. Vzhledem k tomu, že rozšíření podpory params
může být nadměrné, zejména proto, že ostatní jazyky pravděpodobně ještě nějakou dobu nebudou podporovat zpracování nematicových parametrů params
.
Související návrhy
- https://github.com/dotnet/csharplang/issues/1757
- https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params
Související návrhové schůzky
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-08.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-10.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-29.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-31.md#params-collections-evaluation-orders
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-22.md#effect-of-language-version-on-overload-resolution-in-presence-of-params-collections
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-24.md#adjust-dynamic-binding-rules-for-a-situation-of-a-single-applicable-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-01.md#adjust-binding-rules-in-the-presence-of-a-single-candidate
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-03.md#params-collections-and-dynamic
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md#params-span-breaks
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#params-span-breaks
C# feature specifications