Heb jij dat nou ook wel eens? Je krijgt van een collega een assembly met daarin een aantal classes die je in je eigen C# applicatie moet gaan gebruiken. En die classes zijn best ok, maar als ze nou net dat ene stukje functionaliteit erbij zouden hebben, of net die ene method zouden ondersteunen, dan zouden ze fantastisch zijn. Maar ja... binnen je organisatie ben jij de enige die fantastische code oplevert en de rest is maar zo-zo, dus die assembly is niet precies wat je nodig hebt.
Lastig.
Nou hoeft dat in theorie in een object georiënteerde taal als C# helemaal geen probleem te zijn. Immers, een van de voordelen van OO is dat alles herbruikbaar en uitbreidbaar is. Je neemt de class van je collega, leidt er een subclass van af, voegt de juiste functionaliteit aan toe en je bent klaar! Of toch niet?
Nou nee. Het werkt wel zo, maar het is niet de meest praktische methode. Tenminste, niet altijd. Neem bijvoorbeeld de volgende classes:
namespace LameClasses {
public abstract class Person {
// some stuff
}
public class Customer : Person {
// other stuff
}
public class Employee : Person {
// and yet more stuff
}
}
Allemaal leuk en aardig, maar in jouw applicatie heb je code nodig die wat statistische gegevens ophoest van Employee en van Customer. De plek om dat te doen is uiteraard in Person, maar wat als je daar niets aan kunt veranderen? Heb je bijvoorbeeld alleen de dll gekregen en niet de source-code? Of heb je wel de source-code erbij gekregen maar wordt de assembly op dat moment ook door anderen, die geen behoefte hebben aan jouw statistische code, gebruikt? In dat soort gevallen kun je Person niet aan gaan passen. Dus krijg je de volgende code:
namespace ReallyCoolStuff {
public class CoolCustomer: Customer {
public void DoSomeExcitingCode() {
// Put your brilliant code here
}
}
public class CoolEmployee : Employee {
public void DoSomeExcitingCode() {
// Put the same brilliant code here again
}
}
}
Tja... dit is ook geen oplossing, je bent immers dezelfde code twee keer aan het toevoegen. Ok, je kunt een derde class introduceren met daarin de code, en die code dan aanroepen vanuit CoolCustomer en CoolEmployee. Maar als iemand anders nou weer iets toe wil voegen? Krijgen we dan de class EvenCoolerCustomer, EvenCoolerEmployee enzovoorts? Ik hoop het toch niet!
Nog even het probleem samengevat: hoe kan ik een class uitbreiden zonder aan de code van de originele class te komen en toch de nieuwe code zo duidelijk en toegankelijk mogelijk houden?
Het C# team heeft daar een antwoord op: extensions.
Kort gezegd: extensions zijn methods van een class die buiten die class gedefinieerd zijn. Dat werkt als volgt. Denk nog even terug aan ons voorbeeld van Customer en Employee. We moeten daar dus een method DoSomeExcitingCode() aan toevoegen. Om dat te doen in C# 3.0, maak je een nieuwe static class aan (hij moet static zijn!) in een of andere namespace (maakt niet uit welke):
namespace MyExtensions {
public static class MyExtensionClass {
}
}
Zo iets dus. Het maakt eigenlijk niet zo veel uit hoe de class heet, als je je maar aan de coding standards houdt die jouw organisatie hanteert. In deze class definieer je de methods die je aan de niet-perfecte classes wilt toevoegen. Zo dus:
namespace MyExtensions {
public static class MyExtensionClass {
public static void DoSomeExcitingCode() {
// Ah.. mijn briljante code is weer terug!
}
}
}
Tot zover niets spannends, toch? Maar dit gaat niet werken. Nou ja, het gaat wel werken, maar het is nog geen extension. Om deze method nou aan de Person class toe te voegen (en dat willen we immers) moeten we de signature enigzins aanpassen:
namespace MyExtensions {
public static class MyExtensionClass {
public static void DoSomeExcitingCode(this Person person) {
// Doe hier iets met person...
}
}
}
In de body van DoSomeExcitingCode kun je nu aan de slag gaan met person. Maar nu wordt het leuk: zorg ervoor dat de alle namespaces in de using clause heb staan (in mijn voorbeeld dus LameClasses en MyExtensions) en dan:
var medewerker = new Employee();
// En hier komt de magie:
medewerker.DoSomeExcitingCode();
(als je die 'var' niet begrijpt, lees dan mijn vorige posting in mijn blog)
Mmmm.. We roepen DoSomeExcitingCode() aan op het object medewerker, van de class Employee, maar... in de definitie van Employee komt die hele method niet voor! Nee, dat klopt dus. Met het gebruik van 'this Person person' in de parameter lijst van de extension method hebben we aangegeven dat deze method gebruikt mag worden bij alle instances van Person (of afgeleiden!)
Let echter op: het is erg verleidelijk om dit soort code te gaan schrijven:
public static class SuperExtensions {
public static string[] GetAllPrivateFields(this object obj) {
// Ga met reflection door alle private fields heen en geef
// die terug in de string[]... :(
}
}
En nu kun je in alle code, waar je de namespace in je using heb staan waar deze class in staat, GetAllPrivateFields() aanroepen. Want je hebt een uitbreiding op object gemaakt, en aangezien alles van object (inclusief int, bool, char enzovoorts!!!) is afgeleid kun je nu overal deze method toepassen!
Dat komt de leesbaarheid en onderhoudbaarheid van je code niet ten goede. Dus gebruik extensions spaarzaam!
Volgende keer meer over nog meer mogelijkheden in C# 3.0!