Neu in .NET 10.0 [5]: Erweiterungsblöcke in C# 14.0

Mit dem neuen C#-Schlüsselwort Extension können Entwicklerinnen und Entwickler bestehende Klassen erweitern.

vorlesen Druckansicht

(Bild: Pincasso/Shutterstock)

Lesezeit: 2 Min.
Von
  • Dr. Holger Schwichtenberg
close notice

This article is also available in English. It was translated with technical assistance and editorially reviewed before publication.

Die nachträgliche Erweiterbarkeit von Klassen um zusätzliche Methoden gibt es unter dem Namen Extension Methods bereits seit der C#-Sprachversion 3.0, die im Jahr 2007 zusammen mit .NET Framework 3.5 erschien. Dies ist sogar dann möglich, wenn die Klassen bereits an anderer Stelle kompiliert wurden, wie etwa die von Microsoft bereitgestellten Bibliotheken im .NET Framework.

Der Dotnet-Doktor – Holger Schwichtenberg
Der Dotnet-Doktor – Holger Schwichtenberg

Dr. Holger Schwichtenberg ist technischer Leiter des Expertennetzwerks www.IT-Visions.de, das mit 53 renommierten Experten zahlreiche mittlere und große Unternehmen durch Beratungen und Schulungen sowie bei der Softwareentwicklung unterstützt. Durch seine Auftritte auf zahlreichen nationalen und internationalen Fachkonferenzen sowie mehr als 90 Fachbücher und mehr als 1500 Fachartikel gehört Holger Schwichtenberg zu den bekanntesten Experten für .NET und Webtechniken in Deutschland.

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.

Videos by heise

In der .NET-Klassenbibliothek gibt es aus diesem Grund einige Erweiterungsmethoden, die Namen besitzen, die man intuitiv als Property erwarten würde, darunter

  • Enumerable.Count()
  • Queryable.Count()
  • Enumerable.First()
  • Enumerable.Last()

Folgender Beispielcode zeigt die klassischen 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

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).

Der Compiler meckert bei dem Versuch, eine Property mit Setter in einem Erweiterungsblock anzulegen.

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. 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.

Folgendes Codebeispiel zeigt die Erweiterungen für System.String 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!!!
  }
  }
}

Folgender Code zeigt den Aufruf der Erweiterungsmethoden 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 die Size Property versucht, die Zeichenkette neu zuzuweisen!");
  // Das geht nicht, weil die 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
 }

Ausgabe des Beispielcodes

(rme)