Extensiemethoden zijn een fundamenteel onderdeel van C# en Object Georiënteerd Programmeren (OOP). Extensiemethoden in C# stellen je in staat om bestaande typen, waaronder klassen, interfaces, of structuren, “uit te breiden”, zonder dat je de oorspronkelijke code hoeft aan te passen.

Dit is bijzonder handig wanneer je nieuwe functionaliteit wilt toevoegen aan een type dat je niet bezit of niet kunt wijzigen, zoals typen van externe bibliotheken of ingebouwde .NET typen zoals string, List<T>, enzovoort.

In dit artikel leer je hoe je extensiemethoden kunt toevoegen aan je klassen, evenals aan klassen van derden en systeemklassen.

Inhoudsopgave

Hoe DateTime-extensiemethoden te maken

Zeg dat we wat methoden willen die samen met de bestaande DateTime-klasse kunnen worden gebruikt, misschien een methode die aangeeft of het gegeven DateTime-object een weekend is of iets anders.

Extensiemethoden moeten worden gedefinieerd in een statische klasse omdat ze in wezen syntactische suiker zijn die het mogelijk maakt om een statische methode aan te roepen alsof het een instantiemethode is op het type dat je uitbreidt.

Extensiemethoden moeten in een statische klasse zijn omdat:

  1. Geen object nodig: U hoeft geen object te maken om een ​​extensiemethode te gebruiken. Aangezien de methode nieuwe functionaliteit toevoegt aan een bestaand type (zoals string), kan deze werken zonder dat er een instantie van de klasse nodig is.

  2. Georganiseerde code: Het plaatsen van extensiemethoden in een statische klasse houdt de zaken overzichtelijk. Het stelt u in staat om gerelateerde methoden te groeperen, en u kunt ze gemakkelijk in uw code opnemen door de juiste namespace te gebruiken.

Dus, door een statische klasse te gebruiken, kunt u nuttige methoden toevoegen aan bestaande typen zonder hun oorspronkelijke code te wijzigen, en u heeft geen object nodig om ze aan te roepen.

Laten we eerst een statische klasse DateTimeExtensions maken.

public static class DateTimeExtensions {

}

Deze zal alle DateTime-extensies omvatten die we willen maken.

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

Uitleg:

public static bool IsWeekend: Hiermee wordt gedefinieerd dat het een statische methode genaamd IsWeekend is die een bool-waarde (waar/onwaar) zal retourneren.

deze DateTime datum: Het deze trefwoord als een methode-argument geeft aan dat deze methode een extensiemethode is. Dit betekent dat de methode een uitbreiding zal zijn van de DateTime klasse.

Hoe hetzelfde type extensiemethoden te ketenen

Voor een extensiemethode om met andere te worden geketend, moet deze meestal hetzelfde type retourneren als het type dat wordt uitgebreid (of een compatibel type). Dit stelt een andere methode in staat om te worden aangeroepen op het resultaat van de vorige.

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;
}

In het bovenstaande voorbeeld retourneren zowel de ToTitleCase als de TrimAndAppend methoden een stringwaarde, wat betekent dat we de extensiemethoden zoals hieronder kunnen ketenen, die de string naar titelcase zal converteren voordat alle spaties worden verwijderd en de opgegeven string wordt toegevoegd.

Merk op dat we alleen de tweede parameter aan de TrimAndAppend methode hebben gegeven, aangezien de eerste parameter de string is waarop de extensiemethode wordt toegepast (zoals eerder uitgelegd, aangeduid met het deze trefwoord).

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

//Uitvoer:
// Hallo Wereld!!

Als de extensiemethode een ander type retourneert (niet het oorspronkelijke type of een compatibel type), kun je het niet ketenen. Bijvoorbeeld:

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

Om minder voor de hand liggende redenen zal dit niet werken. Wanneer je methoden ketent, worden ze niet geketend vanuit de oorspronkelijke variabele—ze worden geketend vanuit het retourneertype van de vorige methodeaanroep.

Hier hebben we een datum genaamd IsWeekend() die een Boolean retourneert. We hebben vervolgens geprobeerd om AddDays(1) aan te roepen op een Boolean-waarde die niet bestaat, aangezien het een DateTime extensie is. De codecompiler zal falen bij het bouwen en een foutmelding geven die je hierover informeert.

Hoe de instantie teruggeven om te ketenen

In sommige extensiemethoden, vooral die voor configuratie (zoals Dependency Injection), geef je dezelfde instantie terug om method chaining mogelijk te maken. Dit stelt je in staat om door te gaan met het werken met het oorspronkelijke object of zijn gewijzigde staat over meerdere aanroepen, wat een vloeiende interface mogelijk maakt.

Laten we het voorbeeld nemen van een lijst met auto’s.

public static List<T> RemoveDuplicates<T>(this List<T> list)
{
    // Gebruik Distinct om duplicaten te verwijderen en de lijst bij te werken
    list = list.Distinct().ToList();

    // Geef de gewijzigde lijst terug om method chaining mogelijk te maken
    return list;
}

public static List<T> AddRangeOfItems<T>(this List<T> list, IEnumerable<T> items)
{
    // Voeg een reeks items toe aan de lijst
    list.AddRange(items);

    // Geef de gewijzigde lijst terug om method chaining mogelijk te maken
    return list;  
}

Nu we de lijst van deze extensiemethoden hebben teruggegeven, kunnen we extra methoden aan dezelfde lijst ketenen. Bijvoorbeeld, na het verwijderen van duplicaten met RemoveDuplicates(), kunnen we onmiddellijk AddRangeOfItems() aanroepen op dezelfde lijst.

Dus we kunnen iets doen zoals:

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

var availableBrands = existingStock
    .RemoveDuplicates()
    .AddRangeOfItems(new[] { "Lamborghini" }); // nieuwe voorraad beschikbaar

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

// Output: Beschikbare Merken Nu: Ford, Jaguar, Ferrari, Renault, Lamborghini

We hebben duplicaten verwijderd uit een lijst met automerken en nieuwe voorraad toegevoegd aan dezelfde lijst. Dit werkt omdat RemoveDuplicates de lijst retourneert, waardoor we het kunnen koppelen aan AddRangeOfItems.

Als RemoveDuplicates in plaats van de lijst void zou retourneren, zouden we de methoden niet kunnen koppelen. Het zou nog steeds duplicaten verwijderen, maar verdere acties zoals het toevoegen van nieuwe voorraad zouden niet mogelijk zijn in dezelfde expressie.

We zouden ook de RemoveDuplicates moeten bijwerken om het doorgegeven lijstargument bij te werken, aangezien Distinct() een nieuwe lijst retourneert die niet wordt geretourneerd zoals hieronder getoond, wat ik denk dat je het ermee eens zult zijn dat dit veel omslachtiger is.

public static void RemoveDuplicates<T>(this List<T> list)
{
    // Haal de unieke elementen op en wis de originele lijst
    var distinctItems = list.Distinct().ToList();
    list.Clear(); 

    // Voeg de unieke items weer toe aan de originele lijst
    list.AddRange(distinctItems);
}

Waarom kan ik deze methoden niet gewoon aan mijn klasse toevoegen?

Als de methode geen kernonderdeel is van de functionaliteit van de klasse, kan het plaatsen ervan in een extensiemethode helpen om de klasse gefocust en onderhoudbaar te houden.

Scheiding van zorgen: Het gebruik van extensiemethoden houdt je code schoner en helpt de complexiteit te verminderen. Het helpt om de klasse niet op te blazen met methoden die mogelijk niet vaak worden gebruikt.

Verbeteren van externe bibliotheken: Als je een bibliotheek of framework gebruikt waarvan je de broncode niet kunt aanpassen, stellen extensiemethoden je in staat functionaliteit toe te voegen aan die typen zonder hun definities te wijzigen.

Laten we zeggen dat je de FileInfo klasse uit de System.IO namespace gebruikt om met bestanden te werken. Je wilt misschien een methode toevoegen om eenvoudig te controleren of een bestand te groot is (bijvoorbeeld meer dan 1 GB), maar je kunt de FileInfo klasse niet rechtstreeks aanpassen omdat deze behoort tot de System.IO namespace (dat wil zeggen, het is ingebakken in .Net).

Zonder een extensie:

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

if (fileInfo.Length > 1024 * 1024 * 1024) // bestandsgrootte is groter dan 1GB
{
    Console.WriteLine("The file is too large.");
}
else
{
    Console.WriteLine("The file size is acceptable.");
}

Met extensiemethode:

Je kunt dit meer herbruikbaar maken door een extensiemethode toe te voegen die controleert of het bestand groter is dan 1 GB.

public static class FileInfoExtensions
{
    //extensiemethode, met standaard bestandsgrootte van 1GB (kan worden overschreven)
    public static bool IsFileTooLarge(this FileInfo fileInfo, long sizeInBytes = 1024 * 1024 * 1024)
    {
        return fileInfo.Length > sizeInBytes;
    }
}

Nu kan je de IsFileTooLarge methode direct gebruiken op FileInfo objecten, waardoor je code schoner wordt:

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.");
}

Het uitbreiden van externe bibliotheken en pakketten kan je code veel compatibeler maken.

Betere Organisatie & Leesbaarheid: Je kunt extensiemethoden organiseren in statische klassen op basis van functionaliteit of context, waardoor het gemakkelijker wordt om ze te vinden en te gebruiken. Dit wordt zeker versterkt door het toestaan van het ketenen van extensiemethoden.

Wanneer Extensies Gebruiken

  • Voor Hulpmethoden: Als je hulpmethoden hebt die handig zijn voor een type maar niet direct behoren tot het type zelf (bijvoorbeeld, opmaak, validatie).

  • Voor het Verbeteren van Ingebouwde Types: Als je functionaliteit wilt toevoegen aan ingebouwde types (zoals string of DateTime) zonder ze te wijzigen.

  • Wanneer je Methoden Optioneel Wilt Houden: Als je extra methoden wilt bieden die gebruikers kunnen kiezen om te gebruiken zonder hen te dwingen deze in het hoofdklasseontwerp op te nemen.

Voorbeeldscenario

Stel je voor dat je een Person klasse hebt, en je wilt een methode toevoegen om de naam van de persoon mooi te formatteren:

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

// Uitbreidingsmethode in een statische klasse
public static class PersonExtensions
{
    public static string GetFullName(this Person person)
    {
        return $"{person.FirstName} {person.LastName}";
    }
}

Door een uitbreidingsmethode voor GetFullName te gebruiken, kun je de Person klasse eenvoudig en gefocust houden op zijn kernverantwoordelijkheden, terwijl je toch nuttige functionaliteit biedt.

Wanneer geen Gebruik te Maken van Uitbreidingsmethoden

  • Voor Kernfunctionaliteit: Als een methode essentieel is voor het kerngedrag van een klasse, moet deze deel uitmaken van de klasse zelf, niet een uitbreiding.

  • Voor Strakke Koppeling: Als de extensiemethode gedetailleerde kennis van de privéstatus van de klasse vereist of regelmatige toegang tot de interne logica nodig heeft.

  • Voor Openbare API’s: Bij het ontwerpen van een openbare bibliotheek of API is het vaak beter om de benodigde methoden rechtstreeks in de klasse op te nemen in plaats van gebruikers te dwingen hun eigen extensiemethoden te vinden of te maken.

Overwegingen bij het ontwerpen van extensies

Hoewel extensiemethoden krachtig en handig zijn in veel gevallen, zijn er bepaalde nadelen of situaties waarin het gebruik ervan misschien niet de beste keuze is:

Verborgen Gedrag/Verwarring

  • Extensiemethoden verschijnen niet direct in de klassedefinitie, wat betekent dat ze moeilijker te ontdekken zijn voor ontwikkelaars die niet bekend zijn met de beschikbare extensies.

  • Ontwikkelaars moeten weten dat deze extensiemethoden bestaan, anders zouden ze deze kunnen missen, tenzij ze werken in een IDE met functies zoals IntelliSense (bijvoorbeeld Visual Studio, JetBrains Rider). Deze IDE’s kunnen extensiemethoden suggereren vanuit andere bestanden of namespaces wanneer ze het juiste type detecteren. Zonder een IDE met veel functies zou de ontwikkelaar zich bewust moeten zijn van de extensiemethoden of de map moeten vinden waarin ze zijn opgeslagen.

Kan geen private leden benaderen

  • Extensiemethoden kunnen alleen leden (methoden, eigenschappen, velden) benaderen die openbaar of intern zijn.

  • Ze kunnen geen private of beschermde leden van een klasse benaderen omdat extensiemethoden werken alsof ze deel uitmaken van de klasse van buitenaf, vergelijkbaar met reguliere methodeaanroepen van buiten de klasse.

Voorbeeld:

public class Car
{
    private string engineNumber = "12345"; // Privéveld

    public string Brand { get; set; } = "Ford"; // Publieke eigenschap

    private void StartEngine() // Privémethode
    {
        Console.WriteLine("Engine started");
    }
}
public static class CarExtensions
{
    public static void DisplayBrand(this Car car)
    {
        Console.WriteLine($"Brand: {car.Brand}"); // Toegang tot de publieke 'Merk' eigenschap
    }

    public static void TryAccessPrivateField(this Car car)
    {
        // Kan niet toegang krijgen tot de privé 'motorNummer'
        // Dit resulteert in een compileerfout.
        Console.WriteLine(car.engineNumber);
    }
}

Code Dupliceer & Overmatig gebruik

  • In sommige gevallen kunnen extensiemethoden code duplicatie aanmoedigen. Als meerdere projecten of klassen vergelijkbare extensiemethoden vereisen, zou je dezelfde extensiemethoden op verschillende plaatsen kunnen schrijven of kopiëren, wat het moeilijker maakt om de code consistent te beheren en bij te werken.

    Om dit te vermijden, organiseer je code effectief. Ik zou aanbevelen om alle extensies binnen een extensiemap of project te houden, dicht bij de oorsprong (afhankelijk van de ontwerppatronen die binnen je applicatie worden gebruikt).

  • Misbruik van extensies: Als ze te veel worden gebruikt, kunnen ze de globale ruimte vervuilen met methoden die niet per se globaal hoeven te zijn. Dit kan vervuiling van de API van het type veroorzaken, waardoor het moeilijker wordt om te begrijpen wat de kern is van de klasse versus wat er is toegevoegd via extensies.

In sommige gevallen is het beter om functionaliteit in aparte hulpklassen of services te encapsuleren in plaats van deze toe te voegen via extensiemethoden.

Conclusie

Extensiemethoden zijn handig om functionaliteit op een schone en modulaire manier toe te voegen, maar ze kunnen ook verwarring, namespace-conflicten en gebrek aan toegang tot private leden introduceren.

Zoals in het hele artikel benadrukt, hebben ze veel toepassingen en zijn ze zeker een zeer mooie functie van het Dotnet-framework wanneer ze effectief worden gebruikt. Ze moeten worden gebruikt wanneer dat passend is, maar niet als vervanging voor functionaliteit die binnen de klasse zelf hoort.