Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Champion-fråga: https://github.com/dotnet/csharplang/issues/8887
Motivation
Ordlisteuttrycksfunktionen har identifierat ett behov av att samlingsuttryck skickar användardefinierade data för att konfigurera beteendet för den slutliga samlingen. Mer specifikt tillåter ordlistor användare att anpassa hur deras nycklar jämförs, med hjälp av dem för att definiera likhet mellan nycklar och sortering eller hashing (om det gäller sorterade eller hashade samlingar). Det här behovet gäller när du skapar någon form av ordlistetyp (till exempel D d = new D(...), D d = D.CreateRange(...) och till och med IDictionary<...> d = <synthesized dict>)
För att stödja detta föreslås ett nytt with(...arguments...) element som det första elementet i ett samlingsuttryck så här:
Dictionary<string, int> nameToAge = [with(comparer), .. d1, .. d2, .. d3];
- När du översätter till ett
new CollectionType(...)anrop används dessa...arguments...för att fastställa lämplig konstruktor och skickas därefter. - När du översätter till ett
CollectionFactory.Createanrop skickas dessa...arguments...tidigare med elementargumentetReadOnlySpan<ElementType>, som alla används för att fastställa lämpligCreateöverbelastning och skickas därefter. - När du översätter till ett gränssnitt (till exempel
IDictionary<,>) tillåts endast ett enda argument. Den implementerar ett av de välkända BCL-jämförelsegränssnitten och används för att styra nyckeln som jämför semantik för den slutliga instansen.
Den här syntaxen valdes som den:
- Behåller all information inom syntaxen
[...]. Att se till att koden fortfarande tydligt anger att en samling skapas. - Innebär inte att anropa en
newkonstruktor (när det inte är så alla samlingar skapas). - Innebär inte att du skapar/kopierar samlingens värden flera gånger (som ett postfix
with { ... }kan göra. - Inte contort ordning av åtgärder, särskilt med C# konsekventa vänster-till-höger uttryck utvärderingsordning semantik. Den utvärderar till exempel inte de argument som används för att konstruera en samling efter att ha utvärderat de uttryck som används för att fylla samlingen.
- Tvingar inte en användare att läsa till slutet av ett (potentiellt stort) samlingsuttryck för att fastställa grundläggande beteendesemantik. Till exempel att behöva se till slutet av en hundraradsordlista, bara för att hitta det, ja, det var att använda rätt nyckeljäxare.
- Är båda inte subtila, men inte heller överdrivet utförlig. Att till exempel använda
;i stället för,att ange argument är en mycket enkel syntax att missa.with()lägger bara till 6 tecken och sticker enkelt ut, särskilt med syntaxfärgning av nyckelordetwith. - Läser fint. "Det här är ett samlingsuttryck 'med' dessa argument som består av dessa element."
- Löser behovet av jämförelser för både ordlistor och uppsättningar.
- Säkerställer att alla användare behöver skicka argument, eller att alla behov som vi själva har utöver jämförelsen i framtiden redan hanteras.
- Står inte i konflikt med någon befintlig kod (används https://grep.app/ för att söka).
Designfilosofi
I avsnittet nedan beskrivs tidigare diskussioner om designfilosofi. Inklusive varför vissa formulär avvisades.
Det finns två huvudriktningar som vi kan gå in för att ange dessa användardefinierade data. Det första gäller endast specialfallsvärden i jämförelseutrymmet (som vi definierar som typer som ärver från BCL:ns IComparer<T> eller IEqualityComparer<T> typerna). Den andra är att tillhandahålla en generaliserad mekanism för att tillhandahålla godtyckliga argument till det slutliga anropade API:et när du skapar samlingsuttryck. Den primära ordlisteuttrycksspecifikationen visar hur vi skulle kunna göra det förra, medan den här specifikationen syftar till att göra det senare.
Undersökningar av lösningarna för att bara skicka jämförelseobjekt har avslöjat svagheter i deras tillvägagångssätt om vi ville utvidga dem till godtyckliga argument. Till exempel:
Återanvända elementsyntax , som vi gör med formuläret:
[StringComparer.OrdinalIgnoreCase, "mads": 21]. Detta fungerar bra i ett utrymme därKeyValuePair<,>och jämförare inte ärver från vanliga typer. Men det bryter ner i en värld där man kan göra:HashSet<object> h = [StringComparer.OrdinalIgnoreCase, "v"]. Går det vidare med en jämförelse? Eller försöker du placera två objektvärden i uppsättningen?Separera argument jämfört med element med subtil syntax (som att använda semikolon i stället för ett kommatecken för att separera dem i
[comparer; v1]). Detta riskerar mycket förvirrande situationer där en användare av misstag skriver[1; 2](och hämtar en samling som skickar "1" som till exempel argumentet "kapacitet" för enList<>, och endast innehåller det enda värdet "2", när de avsåg[1, 2](en samling med två element).
För att stödja godtyckliga argument anser vi därför att det behövs en mer uppenbar syntax för att tydligare avgränsa dessa värden. Flera andra designproblem har också kommit på detta utrymme. I ingen särskild ordning är följande:
Att lösningen inte är tvetydig och orsakar brytningar med kod som personer sannolikt använder med samlingsuttryck i dag. Till exempel:
List<Widget> c = [new(...), w1, w2, w3];Det här är lagligt i dag, med
new(...)uttrycket "implicit objektskapande" som skapar en ny widget. Vi kan inte återanvända detta för att skicka argument tillList<>konstruktorn eftersom det säkert skulle bryta befintlig kod.Att syntaxen inte sträcker sig till utanför konstruktionen
[...]. Till exempel:HashSet<string> s = [...] with ...;Dessa syntaxer kan tolkas som att samlingen skapas först och sedan återskapas i olika former, vilket innebär flera omvandlingar av data och potentiellt oönskade högre kostnader (även om det inte är vad som genereras).
Det
newsom ett potentiellt nyckelord att använda alls i det här utrymmet är oöndrigt förvirrande. Både eftersom[...]det redan indikerar att ett nytt objekt har skapats och eftersom översättningar av samlingsuttrycket kan gå via API:er som inte är konstruktorer (till exempel mönstret Skapa metod ).Att lösningen inte är överdrivet utförlig. Ett grundläggande värdeförslag för samlingsuttryck är korthet. Så om formuläret lägger till en stor mängd syntaktiska byggnadsställningar känns det som ett steg bakåt och underskrider värdeförslaget att använda samlingsuttryck, jämfört med att anropa till befintliga API:er för att göra samlingen.
Observera att en syntax som new([...], ...) kör afoul av både "2" och "3" ovan. Det får det att se ut som om vi anropar till en konstruktor (när vi kanske inte är det) och det innebär att ett skapat samlingsuttryck skickas till konstruktorn, vilket definitivt inte är det.
Baserat på alla ovanstående har en liten handfull alternativ kommit upp som känns för att lösa behoven av att skicka argument, utan att gå utanför gränserna för målen med samlingsuttryck.
[with(...arguments...)] Design
Syntax:
collection_element
: expression_element
| spread_element
+ | with_element
;
+with_element
+ : 'with' argument_list
+ ;
Det finns en syntaktisk tvetydighet som omedelbart introduceras med denna grammatikproduktion. På samma sätt som tvetydigheten mellan spread_element och expression_element (förklaras här, finns det en omedelbar syntaktisk tvetydighet mellan with_element och expression_element.
Specifikt with(<arguments>) är både exakt produktionstexten för with_element, och kan också nås via expression_element -> expression -> ... -> invocation_expression. Det finns en enkel övergripande regel för collection_elements. Mer specifikt, om elementet lexiskt börjar med tokensekvensen with( så behandlas det alltid som en with_element.
Detta är fördelaktigt på två sätt. Först behöver en kompilatorimplementering bara titta på följande token omedelbart för att avgöra vilken typ av element som ska parsas. För det andra kan en användare trivialt förstå vilken typ av element de har utan att behöva mentalt försöka parsa vad som följer för att se om de bör se det som ett with_element eller ett expression_element.
Exempel
Exempel på hur detta skulle se ut är:
// 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];
Dessa former verkar "läsa" ganska bra. I alla dessa fall är koden "skapar ett samlingsuttryck, "med" följande argument för att vidarebefordra för att kontrollera den slutliga instansen och sedan de efterföljande element som används för att fylla i den. Till exempel skapar den första raden "en lista med strängar "med" en kapacitet på två gånger antalet värden som ska spridas till den"
Det är viktigt att den här koden har liten chans att bli förbisedd som med formulär som: [arg; element], samtidigt som du lägger till minimal verbositet, med stor flexibilitet för att överföra önskade argument.
Detta skulle tekniskt sett vara en icke-bakåtkompatibel ändring eftersom with(...)det kunde ha varit ett anrop till en befintlig metod med namnet with. Men till skillnad från new(...) vilket är ett känt och rekommenderat sätt att skapa implicit skrivna värden, with(...) är det mycket mindre troligt som ett metodnamn, som kör afoul av .Net-namngivning för metoder. I den osannolika händelsen att en användare hade en sådan metod skulle de säkert kunna fortsätta att anropa den befintliga metoden med hjälp @with(...)av .
Vi skulle översätta det här elementet så här with(...) :
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);
Med andra ord skulle de argument_list argumenten skickas till lämplig konstruktor om vi anropar en konstruktor eller till lämplig "skapa-metod" om vi anropar en sådan metod. Vi skulle också tillåta att ett enda argument som ärver från BCL-jämförelsetyperna tillhandahålls när du instansierar en av gränssnittstyperna för målordlistan för att kontrollera dess beteende.
Conversions
Avsnittet konverteringar för samlingsuttryck uppdateras på följande sätt:
> 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.
Observera att de faktiska argumenten argument_list i inte with_element påverkar om konverteringen finns eller inte. Bara närvaron eller frånvaron av with_element sig själv. Intuitionen här är helt enkelt att om samlingsuttrycket skrivs utan en (som [x, y, z]) måste det vara för att kunna anropa konstruktorn utan args. Om den har [with(...), x, y, z] det kan den anropa lämplig konstruktor. Det innebär också att typer som inte kan anropas med en no-argument-konstruktor kan användas med ett samlingsuttryck, men bara om samlingsuttrycket som innehåller ett with_element.
Den faktiska bestämningen av hur en with_element kommer att påverka konstruktionen ges nedan.
Byggnation
Byggnationen uppdateras på följande sätt.
Elementen i ett samlingsuttryck utvärderas i ordning, från vänster till höger. Inom samlingsargument utvärderas argumenten i ordning, från vänster till höger. Varje element eller argument utvärderas exakt en gång, och eventuella ytterligare referenser refererar till resultatet av den första utvärderingen.
Om collection_arguments ingår och inte är det första elementet i samlingsuttrycket rapporteras ett kompileringsfel.
Om argumentlistan innehåller värden med dynamisk typ rapporteras ett kompileringsfel (LDM-2025-01-22).
Konstruktörer
Om måltypen är en struct - eller klasstyp som implementerar System.Collections.IEnumerableoch måltypen inte har någon create-metod och måltypen inte är en allmän parametertyp :
- Överbelastningsmatchning används för att fastställa den bästa instanskonstruktorn från kandidaterna.
- Uppsättningen kandidatkonstruktorer är alla tillgängliga instanskonstruktorer deklarerade på måltypen som gäller för argumentlistan enligt definitionen i tillämplig funktionsmedlem.
- Om en bästa instanskonstruktor hittas anropas konstruktorn med argumentlistan.
- Om konstruktorn har en
paramsparameter kan anropet vara i expanderat format.
- Om konstruktorn har en
- Annars rapporteras ett bindningsfel.
// 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
CollectionBuilderAttribute-metoder
Om måltypen är en typ med en create-metod:
- Överbelastningsmatchning används för att fastställa den bästa skapandemetoden från kandidaterna.
- För varje skapa-metod för måltypen definierar vi en projektionsmetod med en identisk signatur för create-metoden men utan den sista parametern.
- Uppsättningen kandidatprojektionsmetoder är de projektionsmetoder som gäller för argumentlistan enligt definitionen i tillämplig funktionsmedlem.
- Om en metod för bästa projektion hittas anropas motsvarande create-metod med argumentlistan som läggs till med en
ReadOnlySpan<T>som innehåller elementen. - Annars rapporteras ett bindningsfel.
[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: Skapa metoder
För ett samlingsuttryck där måltypsdefinitionen har ett [CollectionBuilder] attribut, är skapandemetoderna följande, uppdaterade från samlingsuttryck: skapa metoder.
Ett
[CollectionBuilder(...)]attribut anger byggarens typ och metodnamn för en metod som ska anropas för att konstruera en instans av samlingstypen.builder-typen måste vara en icke-generisk
classellerstruct.Först bestäms uppsättningen av tillämpliga skapandemetoder
CM. Den består av metoder som uppfyller följande krav:
- Metoden måste ha det namn som anges i attributet
[CollectionBuilder(...)].- Metoden måste definieras på builder-typen direkt.
- Metoden måste vara
static.- Metoden måste vara tillgänglig där samlingsuttrycket används.
- Metodens aritet måste överensstämma med samlingstypens aritet .
- Metoden måste ha en sista parameter av typen
System.ReadOnlySpan<E>, som skickas efter värde.- Det finns en identitetskonvertering, implicit referenskonverteringeller boxningskonvertering från metodens returtyp till samlingstyp.
Metoder som deklareras för bastyper eller gränssnitt ignoreras och ingår inte i den
CMuppsättningen.
För ett samlingsuttryck med en måltyp
C<S0, S1, …>där -typdeklarationenC<T0, T1, …>har en associerad builder-metodB.M<U0, U1, …>()tillämpas de allmänna typargumenten från måltypen i ordning – och från den yttersta innehållstypen till innersta – till builder-metoden.
De viktigaste skillnaderna från den tidigare algoritmen är:
- Skapa metoder kan ha ytterligare parametrar före parametern
ReadOnlySpan<E>. - Flera metoder för att skapa stöds.
Gränssnittsmåltyp
Om måltypen är en gränssnittstyp:
Överlagringsmatchning används för att fastställa den bästa kandidatmetodens signatur.
Uppsättningen med kandidatsignaturer är signaturerna nedan för målgränssnittet som gäller för argumentlistan enligt definitionen i tillämplig funktionsmedlem.
Gränssnitt Kandidatsignaturer IEnumerable<E>IReadOnlyCollection<E>IReadOnlyList<E>()(inga parametrar)ICollection<E>IList<E>List<E>()List<E>(int)
Om en bästa metodsignatur hittas är semantiken följande:
- Kandidatsignaturen för , och är helt enkelt
()och har samma betydelse som att inte ha elementet allswith().IReadOnlyList<E>IReadOnlyCollection<E>IEnumerable<E> - Kandidatens signaturer för
IList<T>ochICollection<T>är signaturer förList<T>()konstruktorer ochList<T>(int)konstruktorer. När du skapar värdet (se Översättning av föränderligt gränssnitt) anropas respektiveList<T>konstruktor. - Annars rapporteras ett bindningsfel.
Dictionary-Interface måltyp
Detta anges här som en del av funktionen som definieras i https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md.
Listan ovan är utökad för att ha följande objekt:
| Gränssnitt | Kandidatsignaturer |
|---|---|
IReadOnlyDictionary<K, V> |
() (inga parametrar)(IEqualityComparer<K>? comparer) |
IDictionary<K, V> |
Dictionary<K, V>()Dictionary<K, V>(int)Dictionary<K, V>(IEqualityComparer<K>)Dictionary<K, V>(int, IEqualityComparer<K>) |
Om en bästa metodsignatur hittas är semantiken som följer:
- Kandidatsignaturerna för
IReadOnlyDictionary<K, V>är()(som har samma betydelse som att inte ha elementetwith()alls) och(IEqualityComparer<K>). Den här jämförelsen används för att korrekt hash och jämföra nycklarna i målordlistan som kompilatorn väljer att skapa (se Översättning av icke-föränderligt gränssnitt). - Kandidatsignaturerna för är signaturer för
IDictionary<T>Dictionary<K, V>(),Dictionary<K, V>(int)Dictionary<K, V>(IEqualityComparer<K>)ochDictionary<K, V>(int, IEqualityComparer<K>)konstruktorer. När du skapar värdet (se Översättning av föränderligt gränssnitt) anropas respektiveDictionary<K, V>konstruktor. - Annars rapporteras ett bindningsfel.
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
Andra måltyper
Om måltypen är någon annan typ rapporteras ett bindningsfel för argumentlistan, även om den är tom.
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
Refsäkerhet
Vi justerar säkerhetsreglerna collection-expressions.md#ref-safety för att ta hänsyn till elementet with() .
Se även §16.4.15 Villkor för säker kontext.
Skapa metoder
Det här avsnittet gäller för samlingsuttryck vars måltyp uppfyller de begränsningar som definieras i CollectionBuilderAttribute-metoder.
Safe-context bestäms genom att ändra en sats från collection-expressions.md#ref-safety (ändringar i fetstil):
- Om måltypen är en referens-struct-typ med en create-metod är samlingsuttryckets safe-context safe-context för en anrop av create-metoden där argumenten är elementargumenten
with()följt av samlingsuttrycket som argument för den sista parametern (parameternReadOnlySpan<E>).
Metodargumenten måste matcha villkoret gäller för samlingsuttrycket. På samma sätt som med safe-context-bestämning ovan tillämpas metodargumenten som matchar villkoret genom att behandla samlingsuttrycket som ett anrop för metoden create, där argumenten är elementargumenten with() följt av samlingsuttrycket som argument för den sista parametern.
Konstruktoranrop
Det här avsnittet gäller för samlingsuttryck vars måltyp uppfyller de begränsningar som definieras i Konstruktorer.
För ett samlingsuttryck av en referens struct-typ av följande formulär:
[with(a₁, a₂, ..., aₙ), e₁, e₂, ..., eₙ]
Samlingsuttryckets säkra kontext är den smalaste av de säkra kontexterna för följande uttryck:
- Ett objektskapandeuttryck
new C(a₁, a₂, ..., aₙ), därCär måltypen - Elementuttrycken
e₁, e₂, ..., eₙ(antingen själva uttrycken eller spridningsvärdet för ett spridningselement).
Metodargumenten måste matcha villkoret gäller för samlingsuttrycket. Villkoret tillämpas genom att behandla samlingsuttrycket som ett objekt som skapar formuläret new C(a₁, a₂, ..., aₙ) { e₁, e₂, ..., eₙ } per low-level-struct-improvements.md#rules-for-object-initializers.
- Uttryckselement behandlas som om de vore initiatorer för samlingselement.
- Spridningselement behandlas på samma sätt genom att tillfälligt anta att det
Char enAdd(SpreadType spread)metod, därSpreadTypeär typen av spridningsvärde.
Besvarade frågor
dynamic Argument
Ska argument med dynamic typ tillåtas? Det kan kräva användning av körningsbindaren för överbelastningsmatchning, vilket skulle göra det svårt att begränsa uppsättningen kandidater, till exempel för insamlingsverktygets fall.
Upplösning: Otillåtna. LDM-2025-01-22
with() icke-bakåtkompatibel ändring
Det föreslagna with() elementet är en icke-bakåtkompatibel ändring.
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) { ... }
Bekräfta att den icke-bakåtkompatibla ändringen är acceptabel och om icke-bakåtkompatibel ändring ska vara kopplad till språkversionen.
Upplösning: Behåll tidigare beteende (ingen icke-bakåtkompilering) när du kompilerar med tidigare språkversion. LDM-2025-03-17
Ska argument påverka konvertering av samlingsuttryck?
Ska samlingsargument och tillämpliga metoder påverka konvertibiliteten för samlingsuttrycket?
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) { ... }
Om argumenten påverkar konvertibiliteten baserat på tillämpliga metoder bör argumenten förmodligen även påverka typinferens.
Print([with(comparer: StringComparer.Ordinal)]); // Print<string>(HashSet<string>)?
Som referens resulterar liknande fall med måltyp i new() fel.
Print<int>(new(comparer: null)); // error: ambiguous
Print(new(comparer: StringComparer.Ordinal)); // error: type arguments cannot be inferred
Upplösning: Samlingsargument bör ignoreras i konverteringar och typinferens. LDM-2025-03-17
Samlingsverktygets metodparameterordning
Ska span-parametern vara före eller efter några parametrar för samlingsargument för insamlingsmetoder för insamlingsmetoder ?
Elementen skulle först tillåta att argumenten deklareras som valfria.
class MySetBuilder
{
public static MySet<T> Create<T>(ReadOnlySpan<T> items, IEqualityComparer<T> comparer = null) { ... }
}
Argument först skulle tillåta att intervallet är en params parameter, för att stödja anrop direkt i expanderat formulär.
var s = MySetBuilder.Create(StringComparer.Ordinal, x, y, z);
class MySetBuilder
{
public static MySet<T> Create<T>(IEqualityComparer<T> comparer, params ReadOnlySpan<T> items) { ... }
}
Upplösning: Parametern span för element ska vara den sista parametern. LDM-2025-03-12
Argument med tidigare språkversion
Rapporteras ett fel för with() vid kompilering med en tidigare språkversion, eller with binder till en annan symbol i omfånget?
Upplösning: Ingen icke-bakåtkompatibel ändring för with ett samlingsuttryck vid kompilering med tidigare språkversioner.
LDM-2025-03-17
Måltyper där argument krävs
Ska konverteringar av samlingsuttryck stödjas till måltyper där argument måste anges eftersom alla konstruktorer eller fabriksmetoder kräver minst ett argument?
Sådana typer kan användas med samlingsuttryck som innehåller explicita with() argument, men det gick inte att använda typerna för params parametrar.
Tänk till exempel på följande typ som skapats från en fabriksmetod:
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) { ... }
}
Samma fråga gäller för när konstruktorn anropas direkt som i exemplet nedan.
För måltyperna där konstruktorn anropas direkt kräver dock konverteringen av samlingsuttryck för närvarande en konstruktor som kan anropas utan argument, men samlingsargumenten ignoreras när konvertibiliteten fastställs.
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) { ... }
// ...
}
Upplösning: Stöd för konverteringar till måltyper där alla konstruktorer eller fabriksmetoder kräver argument och kräver with() för konverteringen.
LDM-2025-03-05
__arglist
Bör __arglist stödjas i with() element?
class MyCollection : IEnumerable
{
public MyCollection(__arglist) { ... }
public void Add(object o) { }
}
MyCollection c;
c = [with(__arglist())]; // ok
c = [with(__arglist(x, y)]; // ok
Upplösning: Inget stöd för __arglist i samlingsargument om det inte är kostnadsfritt.
LDM-2025-03-05
Argument för gränssnittstyper
Bör argument stödjas för gränssnittsmåltyper?
ICollection<int> c = [with(capacity: 4)];
IReadOnlyDictionary<string, int> d = [with(comparer: StringComparer.Ordinal), ..values];
För föränderliga gränssnittstyper är alternativen:
- Använd de tillgängliga konstruktorerna från den välkända typ som krävs för instansation:
List<T>ellerDictionary<K, V>. - Använd signaturer oberoende av specifik typ, till exempel användning
new()ochnew(int capacity)förICollection<T>ochIList<T>(se Konstruktion för potentiella signaturer för varje gränssnitt).
Att använda tillgängliga konstruktorer från en välkänd typ har följande konsekvenser:
- Parameternamn, optional-ness,
params, hämtas direkt från parametrarna. - Alla tillgängliga konstruktorer ingår, även om det kanske inte är användbart för samlingsuttryck, till exempel
List(IEnumerable<T>)vilket skulle tillåtaIList<int> list = [with(1, 2, 3)];. - Uppsättningen konstruktorer kan vara beroende av BCL-versionen.
Recomendation: Använd tillgängliga konstruktorer från välkända typer. Vi har garanterat att vi skulle använda dessa typer, så det här bara "faller ut" och är den tydligaste och enklaste vägen för att konstruera dessa värden.
För icke-föränderliga gränssnittstyper är alternativen liknande:
- Gör ingenting. Denna
- Använd signaturer oberoende av specifik typ, även om det enda scenariot kan vara
new(IEqualityComparer<K> comparer)förIReadOnlyDictionary<K, V>C#14..
Det går inte att använda tillgängliga konstruktorer från någon välkänd typ (strategin för mutable-interface-types) eftersom det inte finns någon relation till någon viss befintlig typ och den slutliga typen som vi kan använda och/eller syntetisera. Därför måste det finnas udda nya krav som kompilatorn kan mappa alla befintliga konstruktorer av den typen (även när den utvecklas) över till den icke-föränderliga instans som den faktiskt genererar.
Recomendation: Använd signaturer oberoende av en viss typ. Och för C# 14 är det bara stöd new(IEqualityComparer<K> comparer) för IReadOnlyDictionary<K, V> eftersom det är det enda icke-föränderliga gränssnittet där vi anser att det är viktigt för användbarhet/semantik att tillåta användare att tillhandahålla detta. Framtida C#-versioner kan överväga att utöka den här uppsättningen baserat på solida motiveringar.
Lösning:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md
Argument stöds för gränssnittsmåltyper. För både föränderliga och icke-föränderliga gränssnitt kommer uppsättningen argument att kureras.
Den förväntade listan (som fortfarande måste vara LDM-ratificerad) är gränssnittsmåltyp
Tomma argumentlistor
Ska vi tillåta tomma argumentlistor för vissa eller alla måltyper?
Ett tomt with() värde motsvarar inte .with() Det kan ge viss konsekvens med icke-tomma fall, men det skulle inte lägga till någon ny funktion.
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?
Lösning:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-12.md#empty-argument-lists
Vi tillåter med() för konstruktortyper och byggtyper som kan anropas utan argument alls, och vi lägger till tomma konstruktorsignaturer för gränssnittstyperna (föränderliga och skrivskyddade). Matriser och intervall tillåter inte med(), eftersom det inte finns några signaturer som passar dem.
Öppna frågor
Slutföra ett öppet problem från https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion
with(...) är en icke-bakåtkompatibel ändring i språket med [with(...)]. Före den här funktionen innebär det ett samlingsuttryck med ett element, vilket är resultatet av att anropa with-invocation-expression. Efter den här funktionen är det en samling som har argument som skickas till den.
Vill vi att den här pausen bara ska inträffa när en användare väljer en specifik språkversion (till exempel C#-14/15?). Med andra ord, om de är på en äldre langversion får de den tidigare parsningslogik, men på den nyare versionen får de den nyare parsningslogik. I Eller vill vi alltid att den ska ha den nyare parsningslogik, även i en äldre langversion?
Vi har tidigare konst för båda strategierna.
required, till exempel, parsas alltid med den nya logiken, oavsett langversion.
record/field Medan, och andra chang sin parsning logik beroende på språkversion.
Slutligen har detta överlappning och påverkan med Dictionary Expressions, vilket introducerar syntaxen key:value för KVP-element. Vi vill etablera det beteende som vi vill ha för alla langversioner, och för [with(...)] på egen hand, och saker som [with(...) : expr] eller [expr : with(...)].
C# feature specifications