Non bisogna mai dimenticare che un extension method è sempre e solo un metodo statico a cui passiamo un oggetto come primo parametro.
Prendiamo in esame un banale (ed inutile) esempio di extension method:
namespace Raf.Extensions
{
public static class StringExtensions
{
public static char GetFirstChar(this string text)
{
return text[0];
}
}
}
Per usarlo, dovremo solo referenziare il namespace con "using" ed invocarlo come fosse un metodo della classe string
using Raf.Extensions;
/* ... */
private void test(string text)
{
Console.WriteLine(text.GetFirstChar());
}
using Raf.Extensions;
/* ... */
p.test("Hello, world");
/* ... */
private void test(string text)
{
Console.WriteLine(text.GetFirstChar());
}
Appare immediatamente chiara la necessità di gestire il caso in cui la stringa valga null o sia di lunghezza zero. In questa eventualità è quindi necessario introdurre un controllo e decidere il valore da stampare.
Se GetFirstChar fosse un metodo membro della classe, il controllo andrebbe necessariamente fatto esternamente, altrimenti il null farebbe esplodere un'eccezione.
Nel caso di un'extension method questo non è vero e quindi potremmo riscrivere il tutto in questo modo:
namespace Raf.Extensions
{
public static class StringExtensions
{
public static char GetFirstChar(this string text)
{
if (string.IsNullOrEmpty(text))
return ' ';
return text[0];
}
}
}
Il non-intuitivo, seppur comodissimo codice, sarebbe il seguente:
using Raf.Extensions;
/* ... */
p.test(null);
/* ... */
private void test(string text)
{
Console.WriteLine(text.GetFirstChar());
}
A vedere questo codice mi verrebbero i capelli dritti perché senza extension method la NullReferenceException sarebbe scontata e invece tutto funziona come desiderato.
Prima di giudicare terrificante questo side-effect, veniamo ad un esempio più utile di quello appena visto.
Diciamo di voler eseguire il parsing di un file xml dove l'attributo "Name" è opzionale. Potremmo scrivere un extension che ha il duplice scopo di eseguire il controllo dell'assenza dell'attributo e fornire allo stesso tempo il valore di default (stringa vuota).
namespace Raf.Extensions
{
public static class XAttributeExtensions
{
public static string GetString(this XAttribute attribute)
{
if (attribute == null)
return string.Empty;
return attribute.Value;
}
}
}
using Raf.Extensions;
/* ... */
var xml1 = new XElement("root", new XAttribute("Name", "Raf")); // <root Name="Raf" />
var xml2 = new XElement("root"); // <root />
p.PrintName(xml1);
p.PrintName(xml2);
/* ... */
private void PrintName(XElement xml)
{
var name = xml.Attribute("Name").GetString();
Console.WriteLine(name);
}
Sono certo che molti storceranno il naso davanti a codice come questo, ma se anche non è vostra intenzione avvalervene, è bene essere coscienti che il linguaggio lo permette e che è comunque codice davanti al quale potremmo sempre trovarci davanti.