Collection-Expression-Argumente in C# 15

Collection-Expression-Argumente in C# 15

- Matt

Seit Collection Expressions schreibe ich Listen und Sets fast nur noch mit eckigen Klammern. Eine Ecke blieb aber hartnaeckig: Sobald ein HashSet<string> gross- und kleinschreibungsunabhaengig vergleichen sollte oder ein Dictionary einen bestimmten StringComparer brauchte, half die kurze Form nicht. Der Comparer wandert in den Konstruktor, und einen Konstruktoraufruf kennt die eckige Klammer nicht. Also zurueck zu new und Objektinitialisierer, und der schoene Spread war weg.

Der Umweg bisher

Ein Set mit eigenem Vergleich sah bei mir so aus:

var namen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var n in quelle)
    namen.Add(n);

Das ist genau der Boilerplate, den die Collection Expression eigentlich abschaffen sollte, nur dass ich ihn wegen des einen Arguments wieder brauchte.

Das neue with()

C# 15, das mit den .NET-11-Previews kommt, fuehrt dafuer ein Argument-Element ein. Es steht als erstes Element in der Klammer und heisst with(...):

HashSet<string> namen = [with(StringComparer.OrdinalIgnoreCase), .. quelle];

Die Argumente in with(...) gehen per Ueberladungsaufloesung an den passenden Konstruktor, danach werden die restlichen Elemente wie gewohnt eingefuegt. Das funktioniert nicht nur fuer Comparer. Wer die Groesse schon kennt, reicht eine Kapazitaet durch und spart das Nachwachsen der internen Puffer:

List<string> namen = [with(capacity: werte.Count * 2), .. werte];

Sogar Schnittstellen sind abgedeckt. Bei IReadOnlyDictionary<K, V> erzeugt der Compiler eine passende Implementierung und schiebt den Comparer hinein:

IReadOnlyDictionary<string, int> d = [with(StringComparer.Ordinal), .. paare];

Bei eigenen Typen mit CollectionBuilderAttribute darf die Create-Methode jetzt Parameter vor dem ReadOnlySpan<T> haben, sodass auch selbstgebaute Sammlungen ein with(...) annehmen.

Wo es nicht greift

Der offensichtliche Haken: Das ist Vorschauzeug. Die Spezifikation steht noch als Proposal, und bis .NET 11 im Herbst erscheint, kann sich am Rand noch etwas verschieben. In stabilem Code wuerde ich mich nicht darauf verlassen.

Dazu ein paar harte Grenzen. with(...) muss das erste Element sein, sonst gibt es einen Compilerfehler. Fuer Arrays und Spans existiert die Form gar nicht, int[] a = [with(length: 4), 1]; wird abgelehnt, weil es keine passende Signatur gibt. Und dynamic als Argument ist verboten, weil sonst die Ueberladungsaufloesung erst zur Laufzeit klar waere.

Ein feiner Punkt, ueber den ich beim Lesen gestolpert bin: Die Argumente werden bei der Typinferenz und der Konvertierung ignoriert. Nur das Vorhandensein von with(...) entscheidet, ob ein Typ ohne parameterlosen Konstruktor ueberhaupt in Frage kommt, die konkreten Werte darin nicht. Wer also auf Ueberladungswahl anhand des Comparers hofft, wird enttaeuscht.

Und weil with(x) frueher auch der Aufruf einer selbstgeschriebenen Methode with als erstes Element sein konnte, ist das streng genommen ein Bruch. Er greift nur unter der neuen Sprachversion, und man kommt mit @with(...) weiter an die alte Methode. Ich habe noch nie eine Methode with genannt, aber jemand da draussen bestimmt.