Extension Block

Eintrag zuletzt aktualisiert am: 15.07.2026

Die nachträgliche Erweiterbarkeit von Klassen (auch wenn diese bereits anderenorts kompiliert sind, z.B. in den von Microsoft gelieferten Klassen in den .NET-Klassenbibliotheken), um zusätzliche Methoden gibt es unter dem Namen "Extension Methods" bereits seit C#-Sprachversion 3.0, welche zusammen mit .NET Framework 3.5 im Jahr 2007 erschienen sind. Man kann mit Extension Methods aber lediglich eine Instanzmethode zu bestehenden Klassen ergänzen. So mussten Entwicklerinnen und Entwickler zwangsweise Konstrukte, die vom Namen her eigentlich Properties waren, leidigerweise als Methoden ausdrücken, siehe IsEmptyClassic() im nächsten Listing.

In der .NET-Klassenbibliothek gibt es aus diesem Grund einige Erweiterungsmethoden, die Namen besitzen, die man intuitiv als Property erwarten würde, z.B.
  • Enumerable.Count()
  • Queryable.Count()
  • Enumerable.First()
  • Enumerable.Last()

Listing: Klassische Erweiterungsmethoden

public static class StringExtensionClassic
{
public static string TruncateClassic(this string s, int count)
{
if (s == null) return "";
if (s.Length <= count) return s;
return s.Substring(0, count) + "...";
}

public static bool IsEmptyClassic(this string s)
=> String.IsNullOrEmpty(s);
}

In C# 14.0 bietet Microsoft nun mit dem neuen Block-Schlüsselwort extension eine verallgemeinerte Möglichkeit der Erweiterung bestehender .NET-Klassen, die Erweiterungsblöcke ("Extension Blocks") oder Erweiterungsmitglieder ("Extension Members") genannt werden.
Das Schlüsselwort extension muss Teil einer statischen, nicht-generischen Klasse auf der obersten Ebene sein (also keine Nested Class). Nach dem Schlüsselwort extension deklariert man den zu erweiternden Typ (Receiver). Im nächsten Listing ist der Receiver die Klasse System.String (alternativ abgekürzt durch den eingebauten Typ string). Alle Methoden und Properties innerhalb des Extension-Blocks erweitern dann den hier genannten Receiver-Typ. Aktuell kann man in diesen Extension-Blöcken folgende Konstrukte verwenden (siehe nächstes Listing):
  • Instanz-Methoden
  • Statische Methoden
  • Instanz-Properties
  • Statische Properties
  • Operatoren

Hinweis: Da es keine Instanzfelder (Fields) in Erweiterungsblöcken gibt, kann man mit Erweiterungsblöcken nicht den Zustand einer Klasse erweitern. Man kann nur bestehende Zustände lesen und verändern (sofern der Typ mutable ist!).

Ein Erweiterungsblock darf beliebig viele Erweiterungsmitglieder enthalten. Eine Klasse darf mehrere Erweiterungsblöcke sowie zusätzlich auch klassische Extension Methods und andere statische Mitglieder enthalten (siehe Listing). Das erlaubt Entwicklerinnen und Entwicklern, in bestehenden Klassen mit klassischen Erweiterungsmethoden nun noch die neuen Erweiterungsblöcke zu implementieren. Es darf auch mehrere Klassen mit Extension-Blöcken für einen Receiver-Typ geben. Außerdem darf ein Erweiterungsblock eine oder mehrere generische Typparameter (optional inklusive Einschränkung mit where) besitzen:
extension<T>(List<T> source) { … }
oder
extension<T>(List<T> source) where T : INumber<T> { … }
Das folgende Listing zeigt eine Klasse mit zwei Erweiterungsblöcken für System.String und List<T>, wobei T auf Zahlen beschränkt ist, durch where T : INumber<T>.

Listing: Erweiterungen für System.String und List<T> mit C# 14.0

public static class MyExtensions
{
// NEU in C# 14.0: // NEU in C# 14.0 Erweiterungsmitglieder (Schlüsselwort extension)
extension(System.String s) // <-- Receiver (Zielklasse).
{
/// <summary>
/// Erweitern um eine Instanz-Methode (alternative Möglichkeit zur bisherigen Syntax)
/// </summary>
public string Truncate(int count)
{
if (s == null) return "";
if (s.Length <= count) return s;
return s.Substring(0, count) + string.Dots;
}

/// <summary>
/// NEU: Erweitern um eine Instanz-Eigenschaft nur mit Getter
/// </summary>
public bool IsEmpty => String.IsNullOrEmpty(s);

/// <summary>
/// NEU: Erweitern um eine Instanz-Eigenschaft mit Getter und Setter
/// </summary>
public int Size
{
get { return s.Length; }
set
{
// Neuzuweisung geht nicht, da Strings immutable sind, funktioniert die Setter-Logik so nicht !!!
if (value < s.Length) s = s.Substring(0, value);
if (value > s.Length) s = s + new string('.', value - s.Length);
}
}

/// <summary>
/// NEU: Erweitern um eine statische Methode
/// </summary>
public static string Create(int count, char c = '.')
{
return new string(c, count);
}

/// <summary>
/// NEU: Erweitern um eine statische Instanz-Eigenschaft
/// </summary>
public static string Dots => "...";

// NEU: Erweitern um eine Operatorüberladung
public static string operator *(string str, int i) // Operatorüberladung
{
return string.Concat(Enumerable.Repeat(str, i)); ;
}

// NEU: Operatorüberladung als Extension und neu ist auch, dass man ++ überladen kann
public void operator ++()
{
s = s + String.Dots; // Das funktioniert so nicht, da Strings immutable sind !!!
}

//public int VersuchEinFieldAnzulegen { get; set; } // nicht erlaubt
//public int VersuchEinPropertyMitFieldAnzulegen { get; set; } // nicht erlaubt
}

//

// Es darf in der gleichen Klasse auch weitere Extensions für andere Typen geben
extension<T>(List<T> source) where T : INumber<T> // <-- Receiver Ein Erweiterungsblock darf eine oder mehrere generische Typparameter (optional inklusive Constraint) besitzen!!!
{
public List<T> WhereGreaterThan(T threshold)
=> source.Where(x => x > threshold).ToList();

public bool IsEmpty
=> !source.Any();

/// <summary>
/// Erweitern um eine Instanz-Eigenschaft mit Getter und Setter
/// </summary>
public int Size
{
get { return source.Count; }
set
{
while (value < source.Count) source.RemoveAt(source.Count - 1);
if (value > source.Count) source.AddRange(Enumerable.Repeat(default(T)!, value - source.Count).ToList());
}
}

// NEU: Operatorüberladung als Extension und neu ist auch, dass man += überladen kann
public void operator +=(int count)
{
source.Size = source.Count + count;
}

// NEU: Operatorüberladung als Extension und neu ist auch, dass man += überladen kann
public void operator -=(int count)
{
source.Size = source.Count - count;
}

// NEU: Operatorüberladung als Extension und neu ist auch, dass man ++ überladen kann
public void operator ++()
{
source.Size += 1;
}
}

//

/// <summary>
/// Es kann auch klassische Erweiterungsmethoden in der gleichen Klasse geben
/// </summary>
public static string TruncateClassic2(this string s, int count)
{
if (s == null) return "";
if (s.Length <= count) return s;
return s.Substring(0, count) + "...";
}

/// <summary>
/// Es kann auch andere statische Methoden in der gleichen Klasse geben
/// </summary>
public static Version ExtensionVersion => new Version(1, 0, 0);
}
Listing: Nutzung der Erweiterungen für die Klassen String und List<int>
public void Run()
{
CUI.Demo(nameof(CS14_ExtensionDemo) + ": String");

string s1 = "Hallo Holger";
Console.WriteLine($"Vorher: {s1}");
string s2 = s1.TruncateClassic(5);
Console.WriteLine($"Nach TruncateClassic(): {s1}"); // Hello...
Console.WriteLine($"IsEmptyClassic():{s2.IsEmptyClassic()}"); // false

string s3 = "Hallo Holger";
Console.WriteLine($"Vorher: {s3}");
string s4 = s3.Truncate(5);
Console.WriteLine($"Nach Truncate(): {s4}"); // Hello...
Console.WriteLine($"IsEmpty:{s4.IsEmpty}"); // false

string s5 = (s1 + "! ") * 3;
Console.WriteLine($"*3: {s5}"); // "Hallo Holger!Hallo Holger!Hallo Holger!"

string s6 = string.Create(5, '#');
Console.WriteLine($"string.Create(5, '#'): {s6}"); // "#####"

#region nicht möglich
CUI.H2("s1.Size = 5 --> das geht nicht, weil das Size Property versucht, die Zeichenkette neu zuzuweisen!");
// Das geht nicht, weil das Size Property versucht, die Zeichenkette neu zuzuweisen!
s1.Size = 5;
Console.WriteLine(s1); // "Hallo Holger" statt wie erwartet "Hallo"
s1++;
Console.WriteLine(s1); // "Hallo Holger" statt wie erwartet "Hallo Holger..."
#endregion

CUI.Demo(nameof(CS14_ExtensionDemo) + ": Collection");

var list = new List<int> { 1, 2, 3, 4, 5 };
var large = list.WhereGreaterThan(3);
Console.WriteLine(large.IsEmpty);
if (large.IsEmpty)
{
Console.WriteLine("Keine Zahlen größer als 3!");
}
else
{
Console.WriteLine(large.Count + " Zahlen sind größer als 3!");
}

CUI.H2("list.Size = 10");
// Das klappt: Die Liste wird auf 10 Elemente mit Nullen aufgefüllt
list.Size = 10;
foreach (var x in list)
{
CUI.OL(x, separator: " = ");
}

CUI.H2("list.Size -= 2");
list.Size -= 2;
bool restart = true;
foreach (var x in list)
{
CUI.OL(x, separator: " = ", restartCounter: restart);
restart = false;
}

CUI.H2("list.Size++");
list.Size++;
restart = true;
foreach (var x in list)
{
CUI.OL(x, separator: " = ", restartCounter: restart);
restart = false;
}
}

Hinweis: Erweiterungsblöcke sind neu in C# 14.0, das offiziell nur für .NET 10.0 von Microsoft unterstützt wird. Erweiterungsblöcke funktionieren aber auch in älteren .NET-Versionen, wenn man <langversion>latest</langversion> in die Projektdatei einträgt. Auf eigene Gefahr!