Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of mappen te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen om mappen te wijzigen.
Kampioensprobleem: https://github.com/dotnet/csharplang/issues/8887
Motivatie
De functie woordenlijstexpressie heeft vastgesteld dat verzamelingsexpressies nodig zijn om door de gebruiker opgegeven gegevens door te geven om het gedrag van de uiteindelijke verzameling te configureren. Met name met woordenlijsten kunnen gebruikers aanpassen hoe hun sleutels zich verhouden, waarbij ze gelijkheid tussen sleutels definiëren en sorteren of hashen (in het geval van respectievelijk gesorteerde of gehashte verzamelingen). Deze behoefte is van toepassing bij het maken van een type woordenlijst (zoals D d = new D(...), D d = D.CreateRange(...) en zelfs IDictionary<...> d = <synthesized dict>)
Ter ondersteuning hiervan wordt een nieuw with(...arguments...) element voorgesteld als het eerste element van een verzamelingsexpressie als volgt:
Dictionary<string, int> nameToAge = [with(comparer), .. d1, .. d2, .. d3];
- Bij het vertalen naar een
new CollectionType(...)aanroep worden deze...arguments...gebruikt om de juiste constructor te bepalen en dienovereenkomstig door te geven. - Bij het vertalen naar een
CollectionFactory.Createaanroep worden deze...arguments...doorgegeven vóór hetReadOnlySpan<ElementType>argument elementen, die allemaal worden gebruikt om de juisteCreateoverbelasting te bepalen en dienovereenkomstig worden doorgegeven. - Bij het vertalen naar een interface (zoals
IDictionary<,>) is slechts één argument toegestaan. Het implementeert een van de bekende BCL-vergelijkingsinterfaces en wordt gebruikt om de sleutel te beheren die semantiek van het uiteindelijke exemplaar vergelijkt.
Deze syntaxis is als volgt gekozen:
- Bewaart alle informatie in de
[...]syntaxis. Ervoor zorgen dat de code nog steeds duidelijk aangeeft dat er een verzameling wordt gemaakt. - Betekent niet dat een
newconstructor wordt aangeroepen (als dat niet de wijze is waarop alle verzamelingen worden gemaakt). - Dit impliceert niet dat de waarden van de verzameling meerdere keren worden gemaakt/gekopieerd (zoals een postfix
with { ... }. - Hiermee wordt de volgorde van bewerkingen niet samengevoegd, met name met C#'s consistente van links naar rechtse expressies die semantiek rangschikken. Het evalueert bijvoorbeeld niet de argumenten die worden gebruikt om een verzameling samen te stellen nadat de expressies zijn geëvalueerd die zijn gebruikt om de verzameling te vullen.
- Dwingt een gebruiker niet af te lezen tot het einde van een (mogelijk grote) verzamelingsexpressie om de belangrijkste gedragssemantiek te bepalen. Als u bijvoorbeeld het einde van een woordenlijst met honderd regels moet zien, hoeft u alleen dat te vinden, ja, het gebruikte de juiste toetsvergelijker.
- Beide zijn niet subtiel, terwijl ze ook niet te uitgebreid zijn. Als u bijvoorbeeld in plaats van
,argumenten aangeeft,;is dit een heel eenvoudig stukje syntaxis dat u moet missen.with()voegt slechts 6 tekens toe en zal gemakkelijk opvallen, met name met syntaxiskleuring van hetwithtrefwoord. - Leest mooi. "Dit is een verzamelingsexpressie met deze argumenten, bestaande uit deze elementen."
- Lost de noodzaak van vergelijkingen voor zowel woordenlijsten als sets op.
- Zorgt ervoor dat elke gebruiker argumenten moet doorgeven of dat alle behoeften die we zelf hebben buiten vergelijkingen in de toekomst, al worden afgehandeld.
- Conflicteren niet met bestaande code (waarmee https://grep.app/ wordt gezocht).
Ontwerp filosofie
In de onderstaande sectie worden eerdere discussies over ontwerp filosofie besproken. Ook waarom bepaalde formulieren zijn afgewezen.
Er zijn twee hoofdrichtingen die we kunnen gebruiken om deze door de gebruiker gedefinieerde gegevens op te geven. De eerste is alleen waarden in de vergelijkingsruimte (die we definiëren als typen die overnemen van de BCL IComparer<T> of IEqualityComparer<T> typen). De tweede is het bieden van een gegeneraliseerd mechanisme voor het leveren van willekeurige argumenten aan de uiteindelijke aangeroepen API bij het maken van verzamelingsexpressies. De expressiespecificatie van de primaire woordenlijst laat zien hoe we de vorige kunnen uitvoeren, terwijl deze specificatie ernaar streeft om de laatste te doen.
Bij onderzoek van de oplossingen voor het doorgeven van vergelijkingen zijn zwakke punten in hun aanpak aan het licht gekomen als we ze wilden uitbreiden naar willekeurige argumenten. Voorbeeld:
Syntaxis van elementen hergebruiken, zoals we doen met het formulier:
[StringComparer.OrdinalIgnoreCase, "mads": 21]. Dit werkt goed in een ruimte waarKeyValuePair<,>en vergelijkingen niet overnemen van algemene typen. Maar het breekt af in een wereld waar men het zou kunnen doen:HashSet<object> h = [StringComparer.OrdinalIgnoreCase, "v"]. Geeft dit een vergelijking door? Of probeert u twee objectwaarden in de set te plaatsen?Argumenten scheiden versus elementen met subtiele syntaxis (zoals het gebruik van een puntkomma in plaats van een komma om deze te scheiden).
[comparer; v1]Dit kan zeer verwarrende situaties opleveren waarbij een gebruiker per ongeluk schrijft[1; 2](en een verzameling krijgt die '1' doorgeeft, bijvoorbeeld het argument 'capaciteit' voor eenList<>, en alleen de enkele waarde '2' bevat), wanneer ze bedoeld[1, 2]zijn (een verzameling met twee elementen).
Daarom moeten we, om willekeurige argumenten te ondersteunen, een duidelijkere syntaxis nodig hebben om deze waarden duidelijker af te bakenen. In deze ruimte zijn ook verschillende andere ontwerpproblemen aan de orde gekomen. In geen bepaalde volgorde zijn dit:
Dat de oplossing niet dubbelzinnig is en breekt met code die mensen waarschijnlijk vandaag gebruiken met verzamelingsexpressies. Voorbeeld:
List<Widget> c = [new(...), w1, w2, w3];Dit is vandaag de dag legaal, waarbij de
new(...)expressie een 'impliciet object maken' is waarmee een nieuwe widget wordt gemaakt. We kunnen dit niet opnieuw gebruiken om argumenten door te geven aanList<>de constructor, omdat deze zeker bestaande code zou breken.Dat de syntaxis niet buiten de
[...]constructie wordt uitgebreid. Voorbeeld:HashSet<string> s = [...] with ...;Deze syntaxis kan worden geïnterpreteerd om te betekenen dat de verzameling eerst wordt gemaakt en vervolgens opnieuw wordt gemaakt in een andere vorm, waarbij meerdere transformaties van de gegevens worden impliceren en mogelijk ongewenste hogere kosten (zelfs als dat niet wordt verzonden).
Dat
newals een potentieel trefwoord om helemaal in deze ruimte te gebruiken, onherstelbaar verwarrend is. Beide omdat[...]al aangeeft dat er een nieuw object wordt gemaakt en omdat vertalingen van de verzamelingsexpressie mogelijk niet-constructor-API's doorlopen (bijvoorbeeld het patroon Methode maken ).Dat de oplossing niet te uitgebreid is. Een kernwaardepropositie van verzamelingsexpressies is kortheid. Dus als het formulier een grote hoeveelheid syntactische scaffolding toevoegt, voelt het zich als een stap terug en onderneemt het waardevoorstel van het gebruik van verzamelingsexpressies, versus het aanroepen van de bestaande API's om de verzameling te maken.
Houd er rekening mee dat een syntaxis zoals new([...], ...) '2' en '3' hierboven wordt uitgevoerd. Hiermee wordt het weergegeven alsof we een constructor aanroepen (als we dat mogelijk niet zijn) en het impliceert dat een gemaakte verzamelingsexpressie wordt doorgegeven aan die constructor, wat zeker niet is.
Op basis van al het bovenstaande zijn er een klein aantal opties gekomen die de behoeften van het doorgeven van argumenten kunnen oplossen, zonder de grenzen van de doelstellingen van verzamelingsexpressies te halen.
[with(...arguments...)] Ontwerp
Syntax:
collection_element
: expression_element
| spread_element
+ | with_element
;
+with_element
+ : 'with' argument_list
+ ;
Er is een syntactische dubbelzinnigheid die onmiddellijk is geïntroduceerd bij deze grammaticaproductie. Vergelijkbaar met de dubbelzinnigheid tussen spread_element en expression_element ( hier uitgelegd, is er een onmiddellijke syntactische dubbelzinnigheid tussen with_element en expression_element.
Specifiek with(<arguments>) is zowel exact de productie-body voor with_element, en is ook bereikbaar via expression_element -> expression -> ... -> invocation_expression. Er is een eenvoudige overkoepelende regel voor collection_elements. Als het element lexisch begint met de tokenreeks with( , wordt het altijd behandeld als een with_element.
Dit is op twee manieren nuttig. Ten eerste moet een compiler-implementatie alleen kijken naar de onmiddellijk volgende tokens die worden weergegeven om te bepalen welk type element moet worden geparseerd. Ten tweede kan een gebruiker triviaal begrijpen wat voor soort element ze hebben zonder dat ze mentaal proberen te parseren wat er volgt om te zien of ze het moeten beschouwen als een with_element of een expression_element.
Voorbeelden
Voorbeelden van hoe dit eruitziet:
// With an existing type:
// Initialize to twice the capacity since we'll have to add
// more values later.
List<string> names = [with(capacity: values.Count * 2), .. values];
Deze formulieren lijken redelijk goed te lezen. In al die gevallen is de code 'het maken van een verzamelingsexpressie, 'met' de volgende argumenten om het uiteindelijke exemplaar door te geven en vervolgens de volgende elementen die worden gebruikt om deze te vullen. De eerste regel 'maakt bijvoorbeeld een lijst met tekenreeksen 'met' een capaciteit van twee keer het aantal waarden dat erin wordt verdeeld'
Belangrijk is dat deze code weinig kans heeft om over het hoofd te worden gezien, zoals bij formulieren zoals: [arg; element], terwijl er ook minimale uitgebreidheid wordt toegevoegd, met een grote hoeveelheid flexibiliteit om eventuele gewenste argumenten mee te geven.
Dit zou technisch gezien een belangrijke wijziging zijn, zoals with(...)een aanroep van een bestaande methode met de naam with. In tegenstelling tot new(...) wat een bekende en aanbevolen manier is om impliciet getypte waarden te maken, is het with(...) echter veel minder waarschijnlijk als een methodenaam, waarbij een afoul van .Net-naamgeving voor methoden wordt uitgevoerd. In het onwaarschijnlijke geval dat een gebruiker een dergelijke methode had, zouden ze zeker kunnen blijven aanroepen in de bestaande methode met behulp van @with(...).
We zouden dit with(...) element als volgt vertalen:
List<string> names = [with(/*capacity*/10), ...]; // translates to:
// argument_list *becomes* the argument list for the
// constructor call.
__result = new List<string>(10); // followed by normal initialization
// or
IList<string> names2 = [with(capacity: 20), ...]; // translates to:
__result = new List<string>(20);
Met andere woorden, de argument_list argumenten worden doorgegeven aan de juiste constructor als we een constructor aanroepen, of naar de juiste 'create method' als we een dergelijke methode aanroepen. We zouden ook toestaan dat één argument dat wordt overgenomen van de BCL-vergelijkingstypen , worden opgegeven bij het instantiëren van een van de interfacetypen van de doelwoordenlijst om het gedrag ervan te bepalen.
Conversies
De sectie conversies voor verzamelingsexpressies wordt op de volgende manier bijgewerkt:
> A struct or class type that implements System.Collections.IEnumerable where:
- * The type has an applicable constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression.
+ a. the collection expression has no `with_element` and the type has an applicable constructor
+ that can be invoked with no arguments, accessible at the location of the collection expression. or
+ b. the collection expression has a `with_element` and the type has at least one constructor
+ accessible at the location of the collection expression.
Let op de werkelijke argumenten binnen de argument_list argumenten die with_element niet van invloed zijn als de conversie al dan niet bestaat. Alleen de aanwezigheid of afwezigheid van het with_element zelf. De intuïtiefheid hier is simpelweg dat als de verzamelingsexpressie zonder één (zoals [x, y, z]) is geschreven, het zou moeten zijn om de constructor zonder argumenten aan te roepen. Als dit het geval is [with(...), x, y, z] , kan het de juiste constructor aanroepen. Dit betekent ook dat typen die niet kunnen worden aangeroepen met een constructor zonder argumenten , kunnen worden gebruikt met een verzamelingsexpressie, maar alleen als die verzamelingsexpressie een with_element.
Hieronder vindt u de daadwerkelijke bepaling van de invloed van een with_elementbouw.
Constructie
De bouw wordt als volgt bijgewerkt.
De elementen van een verzamelingsexpressie worden op volgorde geëvalueerd, van links naar rechts. Binnen verzamelingsargumenten worden de argumenten op volgorde geëvalueerd, van links naar rechts. Elk element of argument wordt precies één keer geëvalueerd en eventuele verdere verwijzingen verwijzen naar de resultaten van deze eerste evaluatie.
Als collection_arguments is opgenomen en niet het eerste element in de verzamelingsexpressie is, wordt een compilatiefout gerapporteerd.
Als de lijst met argumenten waarden met dynamisch type bevat, wordt een compilatiefout gerapporteerd (LDM-2025-01-22).
Constructeurs
Als het doeltype een struct - of klassetype is dat wordt geïmplementeerd System.Collections.IEnumerableen het doeltype geen methode voor maken heeft en het doeltype geen algemeen parametertype is, dan:
- Overbelastingsresolutie wordt gebruikt om de beste instantieconstructor van de kandidaten te bepalen.
- De set kandidaatconstructors is alle toegankelijke exemplaarconstructors die zijn gedeclareerd voor het doeltype dat van toepassing is op de lijst met argumenten , zoals gedefinieerd in het toepasselijke functielid.
- Als er een constructor voor het beste exemplaar wordt gevonden, wordt de constructor aangeroepen met de argumentenlijst.
- Als de constructor een
paramsparameter heeft, kan de aanroep in uitgebreide vorm zijn.
- Als de constructor een
- Anders wordt er een bindingsfout gerapporteerd.
// List<T> candidates:
// List<T>()
// List<T>(IEnumerable<T> collection)
// List<T>(int capacity)
List<int> l;
l = [with(capacity: 3), 1, 2]; // new List<int>(capacity: 3)
l = [with([1, 2]), 3]; // new List<int>(IEnumerable<int> collection)
l = [with(default)]; // error: ambiguous constructor
Methoden CollectionBuilderAttribute
Als het doeltype een type is met een create-methode, doet u het volgende:
- Overbelastingsresolutie wordt gebruikt om de beste methode voor het maken van de kandidaten te bepalen.
- Voor elke create-methode voor het doeltype definiëren we een projectiemethode met een identieke handtekening voor de create-methode, maar zonder de laatste parameter.
- De set kandidaatprojectiemethoden is de projectiemethoden die van toepassing zijn op de lijst met argumenten , zoals gedefinieerd in het toepasselijke functielid.
- Als er een beste projectiemethode wordt gevonden, wordt de bijbehorende create-methode aangeroepen met de argumentenlijst die is toegevoegd aan een
ReadOnlySpan<T>met de elementen. - Anders wordt er een bindingsfout gerapporteerd.
[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> { ... }
class MyBuilder
{
public static MyCollection<T> Create<T>(ReadOnlySpan<T> elements);
public static MyCollection<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> elements);
}
MyCollection<string> c1 = [with(GetComparer()), "1", "2"];
// IEqualityComparer<string> _tmp1 = GetComparer();
// ReadOnlySpan<string> _tmp2 = ["1", "2"];
// c1 = MyBuilder.Create<string>(_tmp1, _tmp2);
MyCollection<string> c2 = [with(), "1", "2"];
// ReadOnlySpan<string> _tmp3 = ["1", "2"];
// c2 = MyBuilder.Create<string>(_tmp3);
CollectionBuilderAttribute: Methoden maken
Voor een verzamelingsexpressie waarbij de definitie van het doeltype een[CollectionBuilder] kenmerk heeft, zijn de volgende methoden gemaakt, bijgewerkt op basis van verzamelingsexpressies: methoden maken.
Een
[CollectionBuilder(...)]kenmerk geeft het opbouwtype en de methodenaam op van een methode die moet worden aangeroepen om een exemplaar van het verzamelingstype te maken.Het opbouwfunctietype moet een niet-generiek
classofstructzijn.Eerst wordt de set van toepasselijke aanmaakmethoden
CMbepaald. Het bestaat uit methoden die voldoen aan de volgende vereisten:
- De methode moet de naam hebben die is opgegeven in het kenmerk
[CollectionBuilder(...)].- De methode moet rechtstreeks worden gedefinieerd op het builder-type.
- De methode moet
staticzijn.- De methode moet toegankelijk zijn wanneer de verzamelingsexpressie wordt gebruikt.
- De arity van de methode moet overeenkomen met de arity van het verzamelingstype.
- De methode moet een laatste parameter van het type
System.ReadOnlySpan<E>hebben, doorgegeven door waarde.- Er is een identiteitsconversie, impliciete verwijzingsconversie, of boksconversie van het methodeteruggavetypenaar het verzamelingstype.
Methoden die zijn gedeclareerd op basistypen of interfaces, worden genegeerd en maken geen deel uit van de
CMset.
Voor een verzamelingsexpressie met een doeltype
C<S0, S1, …>waarbij de declaratie van het typeC<T0, T1, …>een gekoppelde opbouwmethodeB.M<U0, U1, …>()heeft, worden de algemene typeargumenten van het doeltype op volgorde toegepast, van de buitenste naar de binnenste bevattende typen op de opbouwmethode.
De belangrijkste verschillen van het eerdere algoritme zijn:
- Het maken van methoden kan extra parameters vóór de
ReadOnlySpan<E>parameter bevatten. - Er worden meerdere methoden voor maken ondersteund.
Doeltype interface
Als het doeltype een interfacetype is, doet u het volgende:
Overbelastingsresolutie wordt gebruikt om de beste kandidaatmethodehandtekening te bepalen.
De set kandidaathandtekeningen is de onderstaande handtekeningen voor de doelinterface die van toepassing zijn op de lijst met argumenten , zoals gedefinieerd in het toepasselijke functielid.
Koppelvlakken Kandidaathandtekeningen IEnumerable<E>IReadOnlyCollection<E>IReadOnlyList<E>()(geen parameters)ICollection<E>IList<E>List<E>()List<E>(int)
Als er een beste methodehandtekening wordt gevonden, zijn de semantiek als volgt:
- De kandidaathandtekening voor
IEnumerable<E>,IReadOnlyCollection<E>enIReadOnlyList<E>is gewoon()en heeft dezelfde betekenis als hetwith()element helemaal niet heeft. - De kandidaathandtekeningen voor
IList<T>enICollection<T>zijn de handtekeningen vanList<T>()enList<T>(int)constructors. Bij het samenstellen van de waarde (zie Mutable Interface Translation), wordt de respectieveList<T>constructor aangeroepen. - Anders wordt er een bindingsfout gerapporteerd.
Dictionary-Interface doeltype
Dit wordt hier opgegeven als onderdeel van de functie die is gedefinieerd in https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md.
De bovenstaande lijst is uitgebreid met de volgende items:
| Koppelvlakken | Kandidaathandtekeningen |
|---|---|
IReadOnlyDictionary<K, V> |
() (geen parameters)(IEqualityComparer<K>? comparer) |
IDictionary<K, V> |
Dictionary<K, V>()Dictionary<K, V>(int)Dictionary<K, V>(IEqualityComparer<K>)Dictionary<K, V>(int, IEqualityComparer<K>) |
Als er een beste methodehandtekening wordt gevonden, worden de semantiek gevolgd:
- De kandidaathandtekeningen zijn
IReadOnlyDictionary<K, V>()(die dezelfde betekenis hebben als hetwith()element helemaal niet hebben), en(IEqualityComparer<K>). Deze vergelijking wordt gebruikt om de sleutels in de doelwoordenlijst die de compiler maakt op de juiste wijze te hashen en te vergelijken (zie Niet-veranderlijke interfaceomzetting). - De kandidaathandtekeningen zijn
IDictionary<T>de handtekeningen vanDictionary<K, V>(),Dictionary<K, V>(int)Dictionary<K, V>(IEqualityComparer<K>)enDictionary<K, V>(int, IEqualityComparer<K>)constructors. Bij het samenstellen van de waarde (zie Mutable Interface Translation), wordt de respectieveDictionary<K, V>constructor aangeroepen. - Anders wordt er een bindingsfout gerapporteerd.
IDictionary<string, int> d;
IReadOnlyDictionary<string, int> r;
d = [with(StringComparer.Ordinal)]; // new Dictionary<string, int>(StringComparer.Ordinal)
r = [with(StringComparer.Ordinal)]; // new $PrivateImpl<string, int>(StringComparer.Ordinal)
d = [with(capacity: 2)]; // new Dictionary<string, int>(capacity: 2)
r = [with(capacity: 2)]; // error: 'capacity' parameter not recognized
d = [with()]; // Legal: empty arguments supported for interfaces
Andere doeltypen
Als het doeltype een ander type is, wordt er een bindingsfout gerapporteerd voor de lijst met argumenten, zelfs als deze leeg is.
Span<int> a = [with(), 1, 2, 3]; // error: arguments not supported
Span<int> b = [with([1, 2]), 3]; // error: arguments not supported
int[] a = [with(), 1, 2, 3]; // error: arguments not supported
int[] b = [with(length: 1), 3]; // error: arguments not supported
Ref-veiligheid
We passen de regels collection-expressions.md#ref-safety aan om rekening te houden met het with() element.
Zie ook §16.4.15 Veilige contextbeperking.
Methoden maken
Deze sectie is van toepassing op verzamelingsexpressies waarvan het doeltype voldoet aan de beperkingen die zijn gedefinieerd in CollectionBuilderAttribute-methoden.
De veilige context wordt bepaald door een component te wijzigen van collection-expressions.md#ref-safety ( vette wijzigingen):
- Als het doeltype een verwijzingstype is met een create-methode, is de veilige context van de verzamelingsexpressie de veilige context van een aanroep van de create-methode waarbij de argumenten de
with()elementargumenten zijn, gevolgd door de verzamelingsexpressie als het argument voor de laatste parameter (deReadOnlySpan<E>parameter).
De methodeargumenten moeten overeenkomen met de beperking die van toepassing is op de verzamelingsexpressie. Net als bij de bovenstaande bepaling in de veilige context moeten de methodeargumenten overeenkomen met de beperking door de verzamelingsexpressie te behandelen als een aanroep van de methode voor maken, waarbij de argumenten de with() elementargumenten zijn, gevolgd door de verzamelingsexpressie als het argument voor de laatste parameter.
Constructor-aanroepen
Deze sectie is van toepassing op verzamelingsexpressies waarvan het doeltype voldoet aan de beperkingen die zijn gedefinieerd in Constructors.
Voor een verzamelingsexpressie van een verwijzingstype van het volgende formulier:
[with(a₁, a₂, ..., aₙ), e₁, e₂, ..., eₙ]
De veilige context van de verzamelingsexpressie is het smalste van de veilige contexten van de volgende expressies:
- Een expressie
new C(a₁, a₂, ..., aₙ)voor het maken van objecten, waarbijChet doeltype is - De elementexpressies (de expressies
e₁, e₂, ..., eₙzelf of de spreadwaarde in het geval van een spread-element).
De methodeargumenten moeten overeenkomen met de beperking die van toepassing is op de verzamelingsexpressie. De beperking wordt toegepast door de verzamelingsexpressie te behandelen als een object maken van het formulier new C(a₁, a₂, ..., aₙ) { e₁, e₂, ..., eₙ } per laag niveau-struct-improvements.md#rules-for-object-initializers.
- Expressie-elementen worden behandeld alsof ze initialisatiefunctie voor verzamelingselementen zijn.
- Verspreide elementen worden op dezelfde manier behandeld door tijdelijk ervan uit te gaan dat deze
CeenAdd(SpreadType spread)methode heeft, waarbijSpreadTypehet type van de spreadwaarde is.
Beantwoorde vragen
dynamic Argumenten
Moeten argumenten met dynamic type zijn toegestaan? Hiervoor kan het nodig zijn om de runtimebinding te gebruiken voor overbelastingsresolutie, waardoor het lastig zou zijn om de set kandidaten te beperken, bijvoorbeeld voor cases van de opbouwfunctie voor verzamelingen.
Resolutie: Toegestaan. LDM-2025-01-22
with() wijziging die fouten veroorzaken
Het voorgestelde with() element is een belangrijke wijziging.
object x, y, z = ...;
object[] items = [with(x, y), z]; // C#13: ok; C#14: error args not supported for object[]
object with(object x, object y) { ... }
Controleer of de wijziging die fouten veroorzaken acceptabel is en of de wijziging die fouten veroorzaken, moet zijn gekoppeld aan de taalversie.
Resolutie: Houd het vorige gedrag (geen belangrijke wijziging) bij het compileren met een eerdere taalversie. LDM-2025-03-17
Moeten argumenten van invloed zijn op de conversie van verzamelingsexpressies?
Moeten verzamelingsargumenten en de toepasselijke methoden van invloed zijn op de conversie van de verzamelingsexpressie?
Print([with(comparer: null), 1, 2, 3]); // ambiguous or Print<int>(HashSet<int>)?
static void Print<T>(List<T> list) { ... }
static void Print<T>(HashSet<T> set) { ... }
Als de argumenten invloed hebben op de conversiebaarheid op basis van de toepasselijke methoden, moeten argumenten waarschijnlijk ook van invloed zijn op typedeductie.
Print([with(comparer: StringComparer.Ordinal)]); // Print<string>(HashSet<string>)?
Ter referentie: vergelijkbare gevallen met doelgetypeerde new() resultaten in fouten.
Print<int>(new(comparer: null)); // error: ambiguous
Print(new(comparer: StringComparer.Ordinal)); // error: type arguments cannot be inferred
Resolutie: Verzamelingsargumenten moeten worden genegeerd in conversies en typedeductie. LDM-2025-03-17
Parametervolgorde verzamelingsbouwermethode
Voor methoden voor de opbouwfunctie voor verzamelingen moet de spanparameter vóór of na parameters voor verzamelingargumenten zijn?
Met elementen kunnen de argumenten eerst worden gedeclareerd als optioneel.
class MySetBuilder
{
public static MySet<T> Create<T>(ReadOnlySpan<T> items, IEqualityComparer<T> comparer = null) { ... }
}
Argumenten staan eerst toe dat de periode een params parameter is, om oproepen rechtstreeks in uitgevouwen vorm te ondersteunen.
var s = MySetBuilder.Create(StringComparer.Ordinal, x, y, z);
class MySetBuilder
{
public static MySet<T> Create<T>(IEqualityComparer<T> comparer, params ReadOnlySpan<T> items) { ... }
}
Resolutie: De spanparameter voor elementen moet de laatste parameter zijn. LDM-2025-03-12
Argumenten met een eerdere taalversie
Wordt er een fout gerapporteerd voor with() bij het compileren met een eerdere taalversie, of wordt with er een binding met een ander symbool binnen het bereik weergegeven?
Resolutie: Geen belangrijke wijziging voor with binnen een verzamelingsexpressie bij het compileren met eerdere taalversies.
LDM-2025-03-17
Doeltypen waarbij argumenten vereist zijn
Moeten conversies van verzamelingsexpressies worden ondersteund voor doeltypen waarbij argumenten moeten worden opgegeven omdat voor alle constructors of factorymethoden ten minste één argument is vereist?
Dergelijke typen kunnen worden gebruikt met verzamelingsexpressies die expliciete with() argumenten bevatten, maar de typen kunnen niet worden gebruikt voor params parameters.
Denk bijvoorbeeld aan het volgende type dat is samengesteld op basis van een fabrieksmethode:
MyCollection<object> c;
c = []; // error: no arguments
c = [with(capacity: 1)]; // ok
[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> : IEnumerable<T> { ... }
class MyBuilder
{
public static MyCollection<T> Create<T>(ReadOnlySpan<T> items, int capacity) { ... }
}
Dezelfde vraag is van toepassing wanneer de constructor rechtstreeks wordt aangeroepen, zoals in het onderstaande voorbeeld.
Voor de doeltypen waarin de constructor rechtstreeks wordt aangeroepen, vereist de conversie van de verzamelingsexpressie momenteel echter een constructor die zonder argumenten kan worden aangeroepen, maar de verzamelingargumenten worden genegeerd bij het bepalen van de conversiebaarheid.
c = []; // error: no arguments
c = [with(capacity: 1)]; // error: no constructor callable with no arguments?
class MyCollection<T> : IEnumerable<T>
{
public MyCollection(int capacity) { ... }
public void Add(T t) { ... }
// ...
}
Resolutie: Ondersteuning voor conversies naar doeltypen waarbij alle constructors of fabrieksmethoden argumenten vereisen en de conversie vereisen with() .
LDM-2025-03-05
__arglist
Moet __arglist worden ondersteund in with() elementen?
class MyCollection : IEnumerable
{
public MyCollection(__arglist) { ... }
public void Add(object o) { }
}
MyCollection c;
c = [with(__arglist())]; // ok
c = [with(__arglist(x, y)]; // ok
Resolutie: Geen ondersteuning voor __arglist in verzamelingargumenten, tenzij gratis.
LDM-2025-03-05
Argumenten voor interfacetypen
Moeten argumenten worden ondersteund voor interfacedoeltypen?
ICollection<int> c = [with(capacity: 4)];
IReadOnlyDictionary<string, int> d = [with(comparer: StringComparer.Ordinal), ..values];
Voor veranderlijke interfacetypen zijn de volgende opties:
- Gebruik de toegankelijke constructors van het bekende type dat vereist is voor instantatie:
List<T>ofDictionary<K, V>. - Gebruik handtekeningen onafhankelijk van specifiek type, bijvoorbeeld met
new()en voorICollection<T>ennew(int capacity)IList<T>(zie Constructie voor mogelijke handtekeningen voor elke interface).
Het gebruik van de toegankelijke constructors van een bekend type heeft de volgende gevolgen:
- Parameternamen, optioneel,
paramsworden rechtstreeks uit de parameters gehaald. - Alle toegankelijke constructors zijn opgenomen, ook al is dat mogelijk niet handig voor verzamelingsexpressies, zoals
List(IEnumerable<T>)wat zou toestaanIList<int> list = [with(1, 2, 3)];. - De set constructors kan afhankelijk zijn van de BCL-versie.
Aanbevelingen: Gebruik de toegankelijke constructors van de bekende typen. We hebben gegarandeerd dat we deze typen zouden gebruiken, dus dit valt gewoon uit en is het helderste en eenvoudigste pad om deze waarden samen te stellen.
Voor niet-veranderlijke interfacetypen zijn de opties vergelijkbaar:
- Niets doen. Dit
- Gebruik handtekeningen die onafhankelijk zijn van een specifiek type, hoewel het enige scenario voor
IReadOnlyDictionary<K, V>C#14 kan zijnnew(IEqualityComparer<K> comparer).
Het gebruik van toegankelijke constructors van een bekend type (de strategie voor veranderlijke interfacetypen) is niet haalbaar omdat er geen relatie is met een bepaald bestaand type en het uiteindelijke type dat we kunnen gebruiken en/of synthetiseren. Als zodanig moeten er vreemde nieuwe vereisten zijn die de compiler kan toewijzen aan een bestaande constructor van dit type (zelfs als deze zich ontwikkelt) naar het niet-veranderlijke exemplaar dat daadwerkelijk wordt gegenereerd.
Opmerking: Gebruik handtekeningen die onafhankelijk zijn van een specifiek type. En, voor C# 14, alleen ondersteuning new(IEqualityComparer<K> comparer) voor IReadOnlyDictionary<K, V> omdat dat de enige niet-veranderlijke interface is waar we het essentieel vinden voor bruikbaarheid/semantiek om gebruikers dit te laten bieden. Toekomstige C#-releases kunnen overwegen om deze set uit te breiden op basis van solide redenen.
Oplossing:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md
Argumenten worden ondersteund voor interfacedoeltypen. Voor zowel veranderlijke als niet-veranderlijke interfaces wordt de set argumenten gecureerd.
De verwachte lijst (die nog steeds LDM moet worden geratificeerd) is interfacedoeltype
Lege argumentlijsten
Moeten we lege argumentlijsten toestaan voor sommige of alle doeltypen?
Een lege with() waarde zou gelijk zijn aan nee with(). Het kan enige consistentie bieden met niet-lege gevallen, maar er wordt geen nieuwe mogelijkheid toegevoegd.
List<int> l = [with()]; // ok? new List<int>()
ImmutableArray<int> m = [with()]; // ok? ImmutableArray.Create<int>()
IList<int> i = [with()]; // ok? new List<int>() or equivalent
IEnumerable<int> e = [with()]; // ok?
int[] a = [with()]; // ok?
Span<int> s = [with()]; // ok?
Oplossing:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-12.md#empty-argument-lists
We maken het mogelijk met() voor constructortypen en opbouwtypen die helemaal zonder argumenten kunnen worden aangeroepen, en we voegen lege constructorhandtekeningen toe voor de interfacetypen (veranderlijk en alleen-lezen). Matrices en spanen zijn niet toegestaan met(), omdat er geen handtekeningen zijn die erop passen.
Open vragen
Een open bezorgdheid voltooien van https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion
with(...) is een belangrijke wijziging in de taal met [with(...)]. Voor deze functie betekent dit een verzamelingsexpressie met één element, wat het resultaat is van het aanroepen van de with-invocation-expression. Na deze functie is het een verzameling die argumenten bevat die aan deze functie zijn doorgegeven.
Willen we dat deze onderbreking alleen plaatsvindt wanneer een gebruiker een specifieke taalversie (zoals C#-14/15?) kiest. Met andere woorden, als ze een oudere langversion hebben, krijgen ze de eerdere parseringslogica, maar op de nieuwere versie krijgen ze de nieuwere parseringslogica. In Of willen we altijd dat deze de nieuwere parseringslogica heeft, zelfs op een oudere langversion?
We hebben eerdere kunst voor beide strategieën.
required, bijvoorbeeld, wordt altijd geparseerd met de nieuwe logica, ongeacht de langversion.
record/field Terwijl anderen hun parseringslogica aanpassen, afhankelijk van de taalversie.
Ten slotte heeft dit overlap en impact met Dictionary Expressions, waarmee de key:value syntaxis voor KVP-elementen wordt geïntroduceerd. We willen het gedrag bepalen dat we willen voor elke lang-versie, en voor [with(...)] zelfstandig, en dingen zoals [with(...) : expr] of [expr : with(...)].
C# feature specifications