Erweiterungsmethoden sind ein grundlegender Bestandteil von C# und objektorientierter Programmierung (OOP). Erweiterungsmethoden in C# ermöglichen es Ihnen, bestehende Typen wie Klassen, Schnittstellen oder Strukturen zu „erweitern“, ohne deren Originalcode zu ändern.

Dies ist besonders nützlich, wenn Sie neue Funktionalitäten zu einem Typ hinzufügen möchten, den Sie nicht besitzen oder nicht ändern können, wie Typen aus Bibliotheken von Drittanbietern oder integrierte .NET-Typen wie string, List<T> usw.

In diesem Artikel erfahren Sie, wie Sie Erweiterungsmethoden zu Ihren Klassen sowie zu Klassen von Drittanbietern und Systemklassen hinzufügen können.

Inhaltsverzeichnis

Wie man DateTime-Erweiterungsmethoden erstellt

Angenommen, wir möchten einige Methoden, die zusammen mit der bestehenden DateTime-Klasse verwendet werden können, vielleicht eine Methode, die zurückgibt, ob das gegebene DateTime-Objekt ein Wochenende ist oder etwas anderes.

Erweiterungsmethoden müssen in einer statischen Klasse definiert werden, da sie im Wesentlichen syntaktischer Zucker sind, der es Ihnen ermöglicht, eine statische Methode so aufzurufen, als wäre es eine Instanzmethode des Typs, den Sie erweitern.

Erweiterungsmethoden müssen in einer statischen Klasse sein, weil:

  1. Kein Objekt erforderlich: Sie müssen kein Objekt erstellen, um eine Erweiterungsmethode zu verwenden. Da die Methode einer bestehenden Typen (wie string) neue Funktionalität hinzufügt, kann sie ohne eine Instanz der Klasse funktionieren.

  2. Organisierter Code: Die Platzierung von Erweiterungsmethoden in einer statischen Klasse hält die Dinge ordentlich. Es ermöglicht Ihnen, verwandte Methoden zu gruppieren, und Sie können sie leicht in Ihren Code einfügen, indem Sie den entsprechenden Namensraum verwenden.

Durch die Verwendung einer statischen Klasse können Sie nützliche Methoden zu bestehenden Typen hinzufügen, ohne deren ursprünglichen Code zu ändern, und Sie benötigen kein Objekt, um sie aufzurufen.

Zunächst erstellen wir eine DateTimeExtensions statische Klasse.

public static class DateTimeExtensions {

}

Dies wird alle DateTime Erweiterungen umfassen, die wir erstellen möchten.

public static bool IsWeekend(this DateTime date)
{
    return date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}

Erklärung:

public static bool IsWeekend: Dies definiert, dass es sich um eine statische Methode namens IsWeekend handelt, die einen bool Wert (true/false) zurückgibt.

this DateTime date: Das this-Schlüsselwort als Methodenargument zeigt an, dass diese Methode eine Erweiterungsmethode ist. Das bedeutet, dass die Methode eine Erweiterung der DateTime-Klasse sein wird.

So verketten Sie Erweiterungsmethoden des gleichen Typs

Damit eine Erweiterungsmethode mit anderen verkettet werden kann, muss sie in der Regel den gleichen Typ wie derjenige zurückgeben, den sie erweitert (oder einen kompatiblen Typ). Dies ermöglicht es, eine andere Methode auf das Ergebnis der vorherigen aufzurufen.

using System.Globalization;

public static string ToTitleCase(this string str)
{
    return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower());
}

public static string TrimAndAppend(this string str, string toAppend)
{
    return str.Trim() + toAppend;
}

Im obigen Beispiel geben sowohl die ToTitleCase– als auch die TrimAndAppend-Methode einen String-Wert zurück, was bedeutet, dass wir die Erweiterungsmethoden wie unten gezeigt verketten können, die den String in Großbuchstaben umwandelt, bevor sie alle Leerzeichen entfernt und den bereitgestellten String anhängt.

Beachten Sie, dass wir nur den zweiten Parameter an die TrimAndAppend-Methode übergeben haben, da der erste Parameter der String ist, auf den die Erweiterungsmethode angewendet wird (wie zuvor erklärt, durch das this-Schlüsselwort angezeigt).

var title = "hello world   "
    .ToTitleCase()
    .TrimAndAppend("!!");

//Ausgabe:
// Hallo Welt!!

Wenn die Erweiterungsmethode einen anderen Typ zurückgibt (nicht den ursprünglichen oder einen kompatiblen Typ), können Sie sie nicht verketten. Zum Beispiel:

var date = new DateTime();
date.IsWeekend().AddDays(1);

Aus weniger offensichtlichen Gründen wird dies nicht funktionieren. Wenn Sie Methoden verketten, verkettet sie sich nicht von der ursprünglichen Variablen—sie verkettet sich vom Rückgabewert des vorherigen Methodenaufrufs.

Hier haben wir ein Datum namens IsWeekend(), das einen Booleschen Wert zurückgibt. Wir haben dann versucht, AddDays(1) auf einen Booleschen Wert anzuwenden, was nicht existiert, da es eine DateTime-Erweiterung ist. Der Code-Compiler wird beim Erstellen fehlschlagen und einen Fehler ausgeben, der Sie darüber informiert.

Wie man die Instanz zurückgibt, um zu verketten

In einigen Erweiterungsmethoden, insbesondere solchen für die Konfiguration (wie Dependency Injection), geben Sie dieselbe Instanz zurück, um die Methodenkette zu ermöglichen. Dadurch können Sie weiterhin mit dem ursprünglichen Objekt oder dessen modifiziertem Zustand über mehrere Aufrufe hinweg arbeiten, was eine fließende Schnittstelle ermöglicht.

Nehmen wir das Beispiel einer Liste von Autos.

public static List<T> RemoveDuplicates<T>(this List<T> list)
{
    // Verwenden Sie Distinct, um Duplikate zu entfernen und die Liste zu aktualisieren
    list = list.Distinct().ToList();

    // Geben Sie die modifizierte Liste zurück, um die Methodenkette zu ermöglichen
    return list;
}

public static List<T> AddRangeOfItems<T>(this List<T> list, IEnumerable<T> items)
{
    // Fügen Sie eine Reihe von Elementen zur Liste hinzu
    list.AddRange(items);

    // Geben Sie die modifizierte Liste zurück, um die Methodenkette zu ermöglichen
    return list;  
}

Jetzt, da wir die Liste aus diesen Erweiterungsmethoden zurückgegeben haben, können wir zusätzliche Methoden auf der gleichen Liste verketten. Zum Beispiel, nachdem wir Duplikate mit RemoveDuplicates() entfernt haben, können wir sofort AddRangeOfItems() auf der gleichen Liste aufrufen.

Also können wir etwas tun wie:

var existingStock = new List<string> { "Ford", "Jaguar", "Ferrari", "Ford", "Renault" };

var availableBrands = existingStock
    .RemoveDuplicates()
    .AddRangeOfItems(new[] { "Lamborghini" }); // Neuer Bestand verfügbar

Console.WriteLine("Brands Available Now: " + string.Join(", ", availableBrands));

// Ausgabe: Verfügbare Marken jetzt: Ford, Jaguar, Ferrari, Renault, Lamborghini

Wir haben Duplikate aus einer Liste von Automarken entfernt und neue Bestände zur gleichen Liste hinzugefügt. Dies funktioniert, weil RemoveDuplicates die Liste zurückgibt, was es uns ermöglicht, es mit AddRangeOfItems zu verketten.

Wenn RemoveDuplicates anstelle der Liste void zurückgeben würde, könnten wir die Methoden nicht verketten. Duplikate würden immer noch entfernt, aber weitere Aktionen wie das Hinzufügen neuer Bestände wären nicht in demselben Ausdruck möglich.

Wir müssten auch das RemoveDuplicates aktualisieren, um das übergebene Listenargument zu aktualisieren, da Distinct() eine neue Liste zurückgibt, die wie unten gezeigt nicht zurückgegeben wird, was meiner Meinung nach umständlicher ist.

public static void RemoveDuplicates<T>(this List<T> list)
{
    // Die eindeutigen Elemente abrufen und die ursprüngliche Liste löschen
    var distinctItems = list.Distinct().ToList();
    list.Clear(); 

    // Füge die eindeutigen Elemente zurück zur ursprünglichen Liste hinzu
    list.AddRange(distinctItems);
}

Warum kann ich diese Methoden nicht einfach zu meiner Klasse hinzufügen?

Wenn die Methode kein Kernbestandteil der Funktionalität der Klasse ist, kann es helfen, sie in einer Erweiterungsmethode zu platzieren, um die Klasse fokussiert und wartbar zu halten.

Trennung der Anliegen: Die Verwendung von Erweiterungsmethoden hält Ihren Code sauber und hilft, die Komplexität zu reduzieren. Es hilft dabei, die Klasse nicht mit Methoden zu überladen, die möglicherweise selten verwendet werden.

Verbesserung externer Bibliotheken: Wenn Sie eine Bibliothek oder ein Framework verwenden, bei dem Sie den Quellcode nicht ändern können, ermöglichen es Erweiterungsmethoden, Funktionalität zu diesen Typen hinzuzufügen, ohne deren Definitionen zu ändern.

Angenommen, Sie verwenden die FileInfo-Klasse aus dem System.IO-Namespace, um mit Dateien zu arbeiten. Möglicherweise möchten Sie eine Methode hinzufügen, um einfach zu überprüfen, ob eine Datei zu groß ist (zum Beispiel mehr als 1 GB), aber Sie können die FileInfo-Klasse nicht direkt ändern, da sie zum System.IO-Namespace gehört (d. h. sie ist in .Net integriert).

Ohne Erweiterung:

var fileInfo = new FileInfo("myFile.txt");

if (fileInfo.Length > 1024 * 1024 * 1024) // Dateigröße ist größer als 1 GB
{
    Console.WriteLine("The file is too large.");
}
else
{
    Console.WriteLine("The file size is acceptable.");
}

Mit Erweiterungsmethode:

Sie können dies durch Hinzufügen einer Erweiterungsmethode, die überprüft, ob die Datei größer als 1 GB ist, besser wiederverwendbar machen.

public static class FileInfoExtensions
{
    //Erweiterungsmethode mit Standarddateigröße von 1 GB (kann überschrieben werden)
    public static bool IsFileTooLarge(this FileInfo fileInfo, long sizeInBytes = 1024 * 1024 * 1024)
    {
        return fileInfo.Length > sizeInBytes;
    }
}

Jetzt können Sie die Methode IsFileTooLarge direkt auf FileInfo-Objekten verwenden und Ihren Code übersichtlicher gestalten:

csharpCopy codevar fileInfo = new FileInfo("myFile.txt");

if (fileInfo.IsFileTooLarge())
{
    Console.WriteLine("The file is too large.");
}
else
{
    Console.WriteLine("The file size is acceptable.");
}

Das Erweitern von Drittanbieterbibliotheken und -paketen kann Ihren Code viel kompatibler machen.

Bessere Organisation & Lesbarkeit: Sie können Erweiterungsmethoden in statische Klassen basierend auf Funktionalität oder Kontext organisieren, was es einfacher macht, sie zu finden und zu verwenden. Dies wird sicherlich durch die Möglichkeit der Verkettung von Erweiterungsmethoden verbessert.

Wann Erweiterungen Verwenden

  • Für Hilfsmethoden: Wenn Sie Hilfsmethoden haben, die für einen Typ nützlich sind, aber nicht direkt zum Typ selbst gehören (zum Beispiel Formatierung, Validierung).

  • Zur Verbesserung von eingebauten Typen: Wenn Sie Funktionalität zu eingebauten Typen (wie string oder DateTime) hinzufügen möchten, ohne sie zu ändern.

  • Wenn Sie Methoden optional halten möchten: Wenn Sie zusätzliche Methoden bereitstellen möchten, die Benutzer optional verwenden können, ohne sie in das Hauptklassendesign integrieren zu müssen.

Beispielszenario

Stellen Sie sich vor, Sie haben eine Klasse Person und möchten eine Methode hinzufügen, um den Namen der Person schön zu formatieren:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

// Erweiterungsmethode in einer statischen Klasse
public static class PersonExtensions
{
    public static string GetFullName(this Person person)
    {
        return $"{person.FirstName} {person.LastName}";
    }
}

Indem Sie eine Erweiterungsmethode für GetFullName verwenden, können Sie die Klasse Person einfach und auf ihre Hauptverantwortlichkeiten konzentriert halten und gleichzeitig nützliche Funktionalität bereitstellen.

Wann keine Erweiterungsmethoden verwendet werden sollten

  • Für Kernfunktionalität: Wenn eine Methode für das Kernverhalten einer Klasse wesentlich ist, sollte sie Teil der Klasse selbst sein und keine Erweiterung.

  • Für enge Kopplung: Wenn die Erweiterungsmethode ein tiefes Verständnis des privaten Zustands der Klasse erfordert oder regelmäßigen Zugriff auf ihre interne Logik benötigt.

  • Für öffentliche APIs: Bei der Gestaltung einer öffentlich zugänglichen Bibliothek oder API ist es oft besser, notwendige Methoden direkt in die Klasse aufzunehmen, anstatt die Benutzer zu zwingen, ihre eigenen Erweiterungsmethoden zu finden oder zu erstellen.

Dinge, die bei der Gestaltung von Erweiterungen zu beachten sind

Während Erweiterungsmethoden in vielen Fällen mächtig und praktisch sind, gibt es bestimmte Nachteile oder Situationen, in denen ihre Verwendung möglicherweise nicht die beste Wahl ist:

Verstecktes Verhalten/Verwirrung

  • Erweiterungsmethoden erscheinen nicht direkt in der Klassendefinition, was bedeutet, dass sie für Entwickler, die mit den verfügbaren Erweiterungen nicht vertraut sind, schwerer zu entdecken sein können.

  • Entwickler müssen wissen, dass diese Erweiterungsmethoden existieren, sonst könnten sie es versäumen, sie zu verwenden, es sei denn, sie arbeiten in einer IDE mit Funktionen wie IntelliSense (zum Beispiel Visual Studio, JetBrains Rider). Diese IDEs können Erweiterungsmethoden aus anderen Dateien oder Namespaces vorschlagen, während sie den entsprechenden Typ erkennen. Ohne eine funktionsreiche IDE müsste der Entwickler sich der Erweiterungsmethoden bewusst sein oder den Ordner finden, in dem sie gespeichert sind.

Kann nicht auf private Mitglieder zugreifen

  • Erweiterungsmethoden können nur auf Mitglieder (Methoden, Eigenschaften, Felder) zugreifen, die öffentlich oder intern sind.

  • Sie können nicht auf private oder geschützte Mitglieder einer Klasse zugreifen, da Erweiterungsmethoden so funktionieren, als ob sie von außen Teil der Klasse wären, ähnlich wie reguläre Methodenaufrufe von außerhalb der Klasse.

Beispiel:

public class Car
{
    private string engineNumber = "12345"; // Privates Feld

    public string Brand { get; set; } = "Ford"; // Öffentliche Eigenschaft

    private void StartEngine() // Private Methode
    {
        Console.WriteLine("Engine started");
    }
}
public static class CarExtensions
{
    public static void DisplayBrand(this Car car)
    {
        Console.WriteLine($"Brand: {car.Brand}"); // Zugriff auf die öffentliche 'Marke'-Eigenschaft
    }

    public static void TryAccessPrivateField(this Car car)
    {
        // Kann nicht auf die private 'Motornummer' zugreifen
        // Dies wird zu einem Kompilierungsfehler führen.
        Console.WriteLine(car.engineNumber);
    }
}

Code-Duplikation & Übernutzung

  • In einigen Fällen können Erweiterungsmethoden Code-Duplikation fördern. Wenn mehrere Projekte oder Klassen ähnliche Erweiterungsmethoden benötigen, könnten Sie am Ende dieselben Erweiterungsmethoden an verschiedenen Stellen schreiben oder kopieren, was es schwieriger macht, den Code konsistent zu verwalten und zu aktualisieren.

    Um dies zu vermeiden, organisieren Sie Ihren Code effektiv. Ich empfehle, alle Erweiterungen in einem Erweiterungsordner oder -projekt zu halten, nahe am Ursprung (je nach den verwendeten Entwurfsmustern in Ihrer Anwendung).

  • Missbrauch von Erweiterungen: Wenn sie übermäßig verwendet werden, können sie den globalen Raum mit Methoden überladen, die möglicherweise nicht global sein müssen. Dies kann die API des Typs verschmutzen, was es schwieriger macht zu verstehen, was zum Kern der Klasse gehört und was über Erweiterungsmethoden hinzugefügt wurde.

In einigen Fällen ist es besser, Funktionalität in separaten Hilfsklassen oder Diensten zu kapseln, anstatt sie über Erweiterungsmethoden hinzuzufügen.

Fazit

Erweiterungsmethoden sind nützlich, um Funktionalität auf saubere und modulare Weise hinzuzufügen, können jedoch auch Verwirrung, Namensraumkonflikte und mangelnden Zugriff auf private Mitglieder einführen.

Wie im gesamten Artikel hervorgehoben, haben sie viele Verwendungsmöglichkeiten und sind sicherlich ein sehr schönes Feature des Dotnet-Frameworks, wenn sie effektiv eingesetzt werden. Sie sollten angemessen verwendet werden, jedoch nicht als Ersatz für Funktionalität, die innerhalb der Klasse selbst gehört.