Qualche giorno fa ho avuto il piacere di discutere con una conoscenza circa gli extension method del C# 3.0+ e, in senso lato, di tutte le diavolerie che linguaggi come appunto il C# introducono per “aiutare” il programmatore.
Un breve riassunto degli EM…
Partiamo dall’inizio, ossia dalla definizione fornita da Wikipedia:
Extension methods enable you to “add” methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type.
Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type.
For client code written in C# and Visual Basic, there is no apparent difference between calling an extension method and the methods that are actually defined in a type.
Questa definizione sembra un po’ criptica ma l’esempio, sempre tratto da Wikipedia, è più “masticabile”: definiamo una stringa e vogliamo invertirne il contenuto. Ad esempio possiamo chiamare un metodo statico Reverse() di una data classe Utility.
string x = "some string value"; string y = Utility.Reverse(x);
Molto semplice, però non sarà mai immediato come se Reverse() fosse una funzione di string.
string x = "some string value"; string y = x.Reverse(); // more readable...
La classe string non ha quella funzione nè possiamo riaprire il codice e aggiungerla visto che è una classe del linguaggio di cui non abbiamo a disposizione il codice.
In stile OOP, potremmo crearci una classe derivata, mystring, e aggiungere quel metodo. Sfortunatamente la cosa non è praticissima…
public class mystring: string
{
// ...
public static string Reverse()
{
// ...
}
}
… e inoltre, nel caso specifico di string, il codice sopra riportato non compila proprio: la classe string è marcata come sealed, ossia risulta chiusa ed inestendibile.
Il C# mette quindi a disposizione un modo per ovviare a tutte queste problematiche, permette cioè di “simulare” l’aggiunta “dall’esterno” di metodi alle classi, anche a quelle “sigillate” come string: tale meccanismo è noto appunto come “extension method“.
Nel caso di string lo si può sfruttare così, semplicemente:
public static class Utility
{
public static string Reverse(this string input)
{
char[] chars = input.ToCharArray();
Array.Reverse(chars);
return new String(chars);
}
}
Sostanzialmente creiamo una classe statica col metodo che ci interessa. La particolarità è quel “this” anteposto al primo parametro, che definisce proprio il metodo di estensione.
Il risultato di questa strana definizione è proprio quello che ci si aspetta: è come se avessimo aggiunto a string (il primo parametro, quello col this) la funzione Reverse().
string x = "some string value"; string y = x.Reverse(); // now it works...
In modo astuto IntelliSense, il meccanismo di completamento automatico – e non solo – di Visual Studio, mostrerà il “nuovo” metodo come se davvero appartenesse a string.
Veniamo al punto: agevolazioni per il programmatore
Definizione a parte, un po’ particolare se vogliamo, gli extension method sono estremamente comodi da usare per via del fatto che, col supporto di Intellisense in Visual Studio, è virtualmente ininfluente sapere se quel tale metodo è davvero appartenente ad una classe o no. Lo si usa e, come si dice in questi casi, “it just works“.
Personalmente, la possibilità di “estendere” qualcosa di già esistente senza dover ricompilare nulla (e concedere questa possibilità) mi pare una gran bella cosa ma ai puristi della OOP può apparire come una mezza porcheria o, per essere più fini, come un trucco.
E lo è: in realtà un extension method non è altro che una funzione statica esterna alla classe “estesa” ed è il compilatore a garantire l’illusione. Più precisamente si tratta di “syntactic sugar” per risparmiare caratteri e rendere il codice più leggibile e manutenibile.
Nel C# ci sono vari esempi di questo comportamento “pro utente“. Un esempio sono gli accessori per modificare le proprietà (“setter”) con quel parametro “value” implicito.
class A
{
private double p;
public double P
{
get { return p; }
set { p = value; }
}
}
… o perfino l’evoluzione, le property implementate automaticamente:
class A
{
public double P { get; set; }
}
Tutte queste forme “strane” (qualcuno metterebbe anche la sintassi SQL-like di LINQ, come le varie direttive SQL “rimappate” – from, select, … – dal compilatore in chiamate a vari oggetti – .From(), .Select(), … -), probabilmente suonano orripilanti per gli amanti del rigore “accademico” applicato alla programmazione.
Eppure vale la pena impararli perchè rendono davvero il codice più compatto, intellegibile e gestibile. Posso tranquillamente ammettere di essermi ricreduto sul loro valore tanto sul tempo necessario ad impratichirmi quanto nell’impiegarli davvero quando servono.
Computazionalmente parlando non ho notato particolari rallentamenti o altro, per cui credo che rappresentino alternative interessanti e “moderne“, dei sinonimi compatti per evitare di scrivere e riscrivere sempre lo stesso codice “di contorno” (in gergo noto come “boilerplate code“) e potersi concentrare sul codice veramente significativo.
Obiezioni?
Le obiezioni sul loro impiego si sprecano. Una di quelle più ricorrenti è la seguente:
Sfruttare costrutti e sintassi “particolari” può rendere il codice compatto ma “uscendo dalla strada maestra” eventuali successivi lettori del codice (es: altri programmatori), non avvezzi a quel modo di scrivere il codice, potrebbero non capirlo e restare spaesati.
Ciò può davvero essere vero: la “programmazione altezzosa“, per dirla alla Maguire, fatta di forme e stile volutamente complicati per apparire “guru” è sempre un male.
Ma certi “aiuti”, per come sono congegnati, non lo sono. E comunque non è obbligatorio usarli per forza.
Nel caso del C#, le agevolazioni di cui ho parlato rendono il codice non solo compatto ma anche più intuitivo e ciò avviene in modo trasparente.
Ad esempio, tornando alle estensioni, a qualcuno può davvero interessare di sapere se qualcosa è definito in una classe o tramite un’estensione? Salvo casi particolari penso proprio di no, però è estremamente comodo avere un unico modo per accedere a quel qualcosa.
Dualmente, crearne una è veramente facile: sì, ci sono delle condizioni, però alla fine un extension method è un metodo statico in una classe statica con almeno un parametro, il cui tipo è preceduto da this. E’ davvero così complicato scriverne uno?
Credo che a questo punto, comodità alla mano, il lato “fighetto-accademico-purista” possa soprassedere. Idem la pigrizia del dove imparare una cosa in più…
Che ne pensate?
Molto simpatica come feature. In python l’aggiunta, rimozione, sostituzione di metodi a classi e istanze è pratica consolidata (ed estremamente triviale grazie all’estrema dinamicità del linguaggio) anche se non si può fare con i built-in quindi l’esempio che hai fatto con la stringa non è direttamente replicabile (bisogna prima estendere str poi ci fai quello che meglio credi).
[...] This post was mentioned on Twitter by Stefano Castelvetri and Stefano Castelvetri, Gian Paolo Ghilardi. Gian Paolo Ghilardi said: C#, extension method ed altri aiuti al programmatore…: http://wp.me/p9z1q-2qH [...]
E’ vero. Quando si vedono questo tipo di estensioni del linguaggio, l’obiezione sorge quasi spontanea. Belli ma poco ‘puri’. Belli ma danneggiano la comprensione del linguaggio. Belli ma sono solo scorciatoie.
In realtà il problema non si pone. Molti sviluppatori inesperti continuano a non comprendere il linguaggio che stanno usando da anni, così come gli sviluppatori che vogliono approfondirne le meccaniche non saranno di certo bloccati dalla presenza di qualche ‘agevolazione’.
E, infine, fa sempre piacere che si abbia la possibilità di scrivere qualche riga in meno (specie se ridondante), o di rendere più chiara una porzione di codice.
Ciao!
Ciao JP, articolo interessante (anche perché C# non lo uso molto
). In realtà dopo che l’avevo letto mi sarebbe piaciuto provare a vedere se riuscivo a simulare qualcosa del genere in C++… ma nella ricerca durante il “coffee time” ho incontrato un simpatico articolo (http://bit.ly/gGlGEZ) che effettivamente condivido: “è tutto una questione di prospettive”
@Emanuele Rampichini: beh, Python per definizione è un linguaggio dinamico, certamente ancora più dinamico di C# e nonostante dell’introduzione del tipo dynamic nella versione 4.0: cambiare e ricambiare le cose, specialmente a runtime, è una possibilità concreta.
In realtà questo post doveva toccare anche Javascript, altro linguaggio dinamico estremamente “smadruppabile” ma alla fine ho pensato che fosse più opportuno trattare gli extension method del C# come esempio di aiuto al programmatore nel linguaggio e ciò che significa/comporta dal punto di vista “accademico“…
@Stefano: esatto! Era quello lo scopo del post: non tutte le “sintassi” strane sono cose da “fighetti” ma talvolta sono estremamente comode ed utili. E’ bene ricordare che evitare di ripetere per la miliardesima volta lo stesso codice “di contorno” (o sfruttare qualcosa che comporta un simile risparmio) può significare anche evitare di (re)inserire sempre gli stessi errori nel codice…
@Eros: grazie per il link, veramenteinteressante. A dire il vero sospettavo che si potesse fare qualcosa del genere facilmente anche in C++ ma la differenza è che nel C# non si tratta di “emulare una funzionalità” o “exploitarne un’altra” quanto di “sfruttarla” direttamente. E’ quello il punto: in certi casi forme di “sintactic sugar” nel linguaggio diventano “feature” disponibili per tutti, non solo per chi sa che nel C++ il lookup funziona in un certo modo…
Ciao & grazie di essere passati!
Mi ero già imbattuto in una situazione in cui dovevo estendere le classi base e non esitato un attimo ad utilizzare questo scavallo. In questo, ed in molti altri casi, ritengo la comodità un fattore determinante. Un’ immensa pernacchia ai puristi!
@RaS: LOL!