Come creare un extension method per LINQ

Recentemente ho partecipato all’edizione 2024 del WPC di Overnet e in uno dei talk che ho seguito si è parlato, tra le altre cose, di come gli extension methods di LINQ fossero un ottimo modo per semplificare il codice ed aumentarne la sua leggibilità. I metodi di estensione (extension methods) sono una delle funzionalità più potenti e sottovalutate di C# e quando si combinano con LINQ, diventano ancora più utili, permettendo di creare query fluide, leggibili e riutilizzabili. In questo articolo esploreremo come creare un extension method LINQ, perché è utile farlo e come può migliorare il tuo codice C#.

Cosa sono gli Extension Methods di LINQ?

Gli extension methods di LINQ sono metodi statici che estendono il comportamento di tipi che implementano le interfacce IEnumerable<T> o IQueryable<T>. Con LINQ, probabilmente hai già utilizzato metodi di estensione come Where, Select ed OrderBy. Ma quali sono i vantaggi di crearne altri?

Aumentare la manutenibilità: Separare la logica del filtro/trasformazione in unità coese e testabili.

Incoraggiare la riusabilità: E’ un’alternativa a creare funzioni condivise e riutilizzabili, con il vantaggio di utilizzarla inline come siamo già abituati a fare con LINQ.

Migliorare la leggibilità: E’ forse questo l’elemento che più mi ha colpito durante il talk che ho seguito. Il relatore ha fatto un’osservazione molto interessante su come questo aspetto aiutasse anche una figura professionale più junior a comprendere cosa stesse facendo uno specifico blocco di codice.

Vediamo ora qualche esempio di come potremmo andare ad utilizzare quanto descritto sopra.

Nel primo esempio supponiamo di avere una lista di oggetti che rappresentano dei calzini. Siamo nel periodo dei saldi e vogliamo andare a realizzare una logica che vada ad estrarre solamente i calzini con lo sconto >= 50% e che abbiano uno stock di almeno 1000 paia a magazzino. Questa funzione sarà utilizzata in una sezione di un sito web per attirare l’attenzione del cliente, ovviamente il tutto dovrà gestire un concetto di paginazione ed estrarre gli elementi più inerenti al periodo dell’anno (es. Calzini Natalizi se è Natale).

Prima di tutto andiamo a definire il nostro extension method, magari in una sezione del progetto con il codice condiviso tra vari moduli, in modo tale da raggruppare in un punto unico tutti gli extension methods e poterli riutilizzare anche da altre parti.

C#
public static class LinqExtensions
{
    public static IEnumerable<T> GetBigStockAndBigDealSocks<T>(
        this IEnumerable<T> source, 
        int minQuantityInStock,
        int minDiscountPercentage)
    {
        //Verifichiamo la correttezza dei parametri
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (selector == null) throw new ArgumentNullException(nameof(selector));
        if (prefix == null) throw new ArgumentNullException(nameof(prefix));

        return source.Where(item => item.DiscountPercentage >= minDiscountPercentage && item.UnitInStock >= minQuantityInStock);
    }
}

Ricordiamoci sempre che i byte non li paghiamo (in senso stretto) ed è quindi importante dare dei nomi sensati alle funzioni. Il tempo che risparmiate a scrivere il nome di una funzione lo perderete successivamente quando andrete a rileggere o fare refactoring del codice!

Ora non ci basta che andare a richiamare questa funzione all’interno di una nostra funzione (per esempio applicato ad EntityFramework):

C#
int minQuantityInStock = 1000;
int minDiscountPercentage = 50;

var list = _db.Socks
              .GetBigStockAndBigDealSocks(minQuantityInStock, minDiscountPercentage)
              .ToList();

L’esempio che abbiamo utilizzato è ovviamente banale, però già solo così si intuisce quanto il codice possa rimanere pulito e molto più leggibile, senza considerare che potremmo creare degli unit test dedicati per extension methods.

Aggiungiamo ora un altro metodo, che si occupi di estrarre solo i calzini a tema di Natale se siamo in periodo Natalizio:

C#
public static class LinqExtensions
{
    public static IEnumerable<T> FilterByPeriod<T>(this IEnumerable<T> source)
    {
        if(IsChristmasPeriod())
        {
          return source.Where(socks => socks.Tags.Contains("CHRISTMAS"));
        }
        else
        {
          return source;
        }
    }
}

Aggiungiamo questa funzione a quanto abbiamo scritto prima:

C#
int minQuantityInStock = 1000;
int minDiscountPercentage = 50;

var list = _db.Socks
              .GetBigStockAndBigDealSocks(minQuantityInStock, minDiscountPercentage)
              .FilterByPeriod()
              .ToList();

Prima di concludere aggiungiamo un ultimo metodo per gestire la paginazione. Come prima cosa definiamo il metodo:

C#
public static class LinqExtensions
{
  public static IEnumerable<T> Paginate<T>(
          this IEnumerable<T> source, 
          int page, 
          int pageSize)
      {
          if (source == null) throw new ArgumentNullException(nameof(source));
          if (page < 1) throw new ArgumentOutOfRangeException(nameof(page), "La pagina deve essere maggiore di 0.");
          if (pageSize < 1) throw new ArgumentOutOfRangeException(nameof(pageSize), "La dimensione della pagina deve essere maggiore di 0.");
  
          return source.Skip((page - 1) * pageSize).Take(pageSize);
      }
}

Ora aggiungiamo a quanto fatto prima per concludere la nostra funzione:

C#
int minQuantityInStock = 1000;
int minDiscountPercentage = 50;
int pageNumber = 1;
int elementInPage = 4;

var list = _db.Socks
              .GetBigStockAndBigDealSocks(minQuantityInStock, minDiscountPercentage)
              .ChristmasSocksIfIsChristmasPeriod()
              .Paginate(pageIndex, elementInPage)
              .ToList();

Come potete vedere la leggibilità del metodo è estremamente alta e chiunque intuisce il risultato che si possa ottenere da questa funzione. Quale sarebbe stata l’alternativa senza extension methods? Eccola qui:

C#
int minQuantityInStock = 1000;
int minDiscountPercentage = 50;
int pageNumber = 1;
int elementInPage = 4;

var list = _db.Socks
                .Where(socks => socks.DiscountPercentage >= minDiscountPercentage && 
                socks.UnitInStock >= minQuantityInStock
                )
                .Skip((page - 1) * pageSize)
                .Take(pageSize)
                .ToList();
if(IsChristmasPeriod())
{
  list = list.Where(socks => socks.Tags.Contains("CHRISTMAS"));
}

Immaginate ora di avere una figura Junior al vostro fianco (o di essere voi stessi a dover rivedere il codice dopo qualche anno), non sarà forse più facile leggerlo con gli extension methods?

Conclusioni

Creare un extension method LINQ è un modo efficace per migliorare la leggibilità, la riusabilità e la manutenibilità del tuo codice. Incapsulare logiche complesse in metodi di estensione aiuta a ridurre il codice duplicato e a creare query più eleganti. Che tu stia filtrando, paginando o eliminando duplicati, gli extension methods possono essere il tuo asso nella manica per scrivere codice .NET pulito e robusto. Non dimentichiamoci inoltre quanto tutto questo possa facilitare la scrittura di unit test!

Condividi questo articolo
Shareable URL
Post precedente

Alla scoperta di Span<T>

Prosimo post

Principio di programmazione SOLID

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Leggi il prossimo articolo