Riprendiamo da dove eravamo arrivati, e scriviamo il nostro primo metodo funzionale.
Implementiamo i metodi IsMatch e ForAll visti nell’esempio del post precedente.
Per prima cosa abbiamo bisogno di dichiarare una classe statica, infatti i nostri metodi saranno degli extension methods inoltre facciamo in modo tale che il nostro metodo lavori sulle collezioni di oggetti.
public static class PatternMatchingExtensions
{
...
}
Inseriamo a questo punto la dichiarazione dell’extension method IsMatch
[Description("Search for items that match the pattern. Use Regular Expressions to check the pattern")]
public static IEnumerable<string> IsMatch(this IEnumerable<string> items, string pattern)
{
...
}
come vediamo, il nostro primo metodo estende una lista di stringhe per effettuare la ricerca e restituisce una lista di stringhe.
Il codice a questo punto è abbastanza semplice:
[Description("Search for items that match the pattern. Use Regular Expressions to check the pattern")]
public static IEnumerable<string> IsMatch(this IEnumerable<string> items, string pattern)
{
if (string.IsNullOrEmpty(pattern))
throw new ArgumentNullException("pattern");
Regex rx = new Regex(pattern);
List<string> result = new List<string>();
foreach (string item in items)
if (!string.IsNullOrEmpty(item))
if (rx.IsMatch(item))
result.Add(item);
return result;
}
Si tratta di un banalissimo metodo che cicla la lista e restituisce il risultato memorizzato in una nuova lista.
Ovviamente potevamo scriverlo anche in maniera più coincisa utilizzando la sintassi di linq.
Spingiamoci ancora un pochino più avanti e creiamo un overload del metodo IsMatch
[Description("Search for items that match the pattern. Use Regular Expressions to check the pattern")]
public static IEnumerable<T> Match<T>(this IEnumerable<T> items, Func<T, string> selector, string pattern)
{
if (string.IsNullOrEmpty(pattern))
throw new ArgumentNullException("pattern");
if (selector == null)
throw new ArgumentNullException("selector");
Regex rx = new Regex(pattern);
List<T> result = new List<T>();
foreach (T item in items)
if (item != null)
if (rx.IsMatch(selector(item)))
result.Add(item);
return result;
}
Come vediamo in questo overload abbiamo esteso una lista di oggetti generici di cui attualmente non conosciamo il tipo. Abbiamo affidato il compito di ricavare il valore su cui verrà effettuato il match al metodo selector che si appoggia al delegato Func<T, string> per poi valutare tutto come prima attraverso il pattern.
Chi ha letto il mio post su Generici, Extension Methods e lambda expression sa perfettamente che non ci sarà bisogno di indicare nessun tipo mentre utilizziamo questo extension method perchè il compilatore è in grado di inferire tutti i tipi generici che utilizziamo. Quindi l’utilizzo del nostro nuovo metodo sarà:
( facciamo finta di avere una lista di oggetti customers con una proprietà Address)
List<customers> cust = new List<customers>();
...
cust.IsMatch(c => c.Address, ".*Annunzio.*");
stiamo cercando direttamente nella proprietà address attraverso una regex tutte le vie che hanno la parola “Annunzio” al loro interno.
Bene, scriviamo il metodo ForAll
Anche in questo caso dobbiamo estendere una lista di oggetti ed anche in questo caso sceglieremo IEnumerable<T> come tipo base.
[Description("Iterate throw the entire list of elements and call the action method foreach element in the list")]
public static IEnumerable<T> ForAll<T>(this IEnumerable<T> collection, Do<T> action)
{
foreach (T item in collection) action(item);
return collection;
}
Il delegato Do<T> ci permetterà di inserire una lambda expression. La firma del delegato è:
public delegate void Do<T>(T item);
Come vedete il metodo ForAll è ancora più semplice del metodo IsMatch, infatti è semplicemente un foreach che richiama la nostra lambda expression.
In questo caso non manipoliamo direttamente i dati e quindi invece di ritornare void ritorniamo gli stessi dati che abbiamo avuto in ingresso, e quindi IEnumerable<T>.
Torniamo all’esempio della prima parte:
Immaginiamo che dobbiamo cercare tutti i customers con indirizzo “Annunzio” e dobbiamo modificare il CAP a 06100 per poi salvare il risultato nel DB.
List<customers> cust = new List<customers>();
...
cust.IsMatch(c => c.Address, ".*Annunzio.*")
.ForAll(c => c.CAP = "06100");
Con linq avremmo dovuto scrivere qualcosa di più complesso di così.
Ovviamente il segreto è avere una libreria di metodi già pronti da essere utilizzati in modo funzionale, come abbiamo visto infatti la maggior parte delle funzioni del framework .Net non è pensata per la programmazione funzionale semplicemente.
Da quello che ho letto e trovato, il team di F# oltre ovviamente a definire la sintassi il compilatore etc. del nuovo linguaggio ha scritto molti metodi che possono essere utilizzati con questo paradigma di programmazione.
Scrivere una libreria di funzioni in C# come abbiamo visto non è difficile e può essere utilizzata come libreria di base per lo sviluppo di applicazioni generiche permettendoci di sostituire anche i foreach con delle banalissime labda expressions.
Ritorniamo ancora una volta al nostro esempio e degli indirizzi che contengono la parola “Annunzio” vogliamo quelli che contengono la città Perugia, in quel caso inseriamo un flag per indicare che il cliente è un cliente locale. (l’esempio è assolutamente tirato ad indovinare)
List<customers> cust = new List<customers>();
...
cust.IsMatch(c => c.Address, ".*Annunzio.*")
.ForAll(c => c.CAP = "06100")
.IsMatch(c => c.Citta, "Perugia")
.ForAll(c => c.Locale = true);
Come possiamo vedere la scrittura non si interrompe ed ogni istruzione riparte dal risultato della istruzione precedente.
Spero di avervi stuzzicato l’appetito, come è successo a me la prima volta che ho visto questo modo di scrivere codice.
E spero che qualcuno di voi sia così interessato da darmi una mano a scrivere la prima libreria per la programmazione funzionale in C#.
Ovviamente poi la postiamo su code Plex