C# 14 Erweiterungsblöcke und Extension-Properties
- Matt
Erweiterungsmethoden gibt es seit C# 3, und die meisten von uns haben sie längst verinnerlicht: ein this vor dem ersten Parameter, und schon hängt die Methode scheinbar am fremden Typ. Was nie ging, war eine Eigenschaft. Wenn ich für IEnumerable<int> einen Median wollte, musste ich Median() mit Klammern schreiben, obwohl es sich beim Aufruf wie ein Wert liest, nicht wie eine Aktion. Der Grund ist banal: Eine Eigenschaft hat keinen Parameter, und der Empfänger einer alten Erweiterungsmethode steckte genau dort.
Der Block statt des this-Parameters
C# 14 dreht das um. Der Empfänger wird nicht mehr pro Methode wiederholt, sondern einmal für einen ganzen Block deklariert:
public static class Sequenzen
{
extension(IEnumerable<int> quelle)
{
public IEnumerable<int> AddValue(int summand)
=> quelle.Select(x => x + summand);
}
}
quelle ist innerhalb des Blocks überall sichtbar, jedes Element im Block spricht denselben Empfänger an. Der Aufruf bleibt identisch zur alten Schreibweise, und der erzeugte IL-Code auch. Beide Formen sind binär- und quellkompatibel, man kann bestehende Erweiterungsmethoden also ohne Bruch umstellen.
Endlich auch Eigenschaften
Der eigentliche Zugewinn steht im selben Block, denn er nimmt jetzt auch Eigenschaften:
extension(IEnumerable<int> quelle)
{
public int Median
{
get
{
var sortiert = quelle.OrderBy(n => n).ToList();
int mitte = sortiert.Count / 2;
return sortiert.Count % 2 == 0
? (sortiert[mitte - 1] + sortiert[mitte]) / 2
: sortiert[mitte];
}
}
}
Aufgerufen wird das als zahlen.Median, ohne Klammern. Auch statische Extensions sind möglich, dann bleibt der Parametername weg und man ruft IEnumerable<int>.Identity auf, als wäre es ein statisches Mitglied des Typs. Neben Methoden und Eigenschaften erlaubt der Block Indexer und Operatoren.
Wo es noch nicht reicht
Felder gibt es nicht. Ein Erweiterungsblock kann kein Hintergrundfeld anlegen, deshalb bleibt echter Zustand am fremden Typ ausgeschlossen. Eine berechnete Eigenschaft wie Median läuft bei jedem Zugriff neu, hier hilft kein field wie beim field-Schlüsselwort innerhalb echter Eigenschaften.
Der zweite Haken ist die Signatur. Ein Block eröffnet keinen eigenen Namensraum: Alle Mitglieder einer Klasse müssen sich unterscheiden, auch über mehrere Blöcke hinweg, wobei der Empfängertyp in die Signatur eingeht. Kollidiert dann doch etwas, muss man die vom Compiler erzeugte Methode direkt über den Klassennamen aufrufen, etwa Sequenzen.get_Median(zahlen), was jede Eleganz sofort wieder kassiert.
Und es braucht C# 14, also .NET 10. Für ältere Zielframeworks bleibt es beim this-Parameter, der ja weiterhin funktioniert und für reine Methoden auch nichts Schlechteres ist.