Quale interfaccia usare per ritornare un insieme?

19 giu 2015 22.44

_Logo Una buona pratica quando si ritorna un insieme da una funzione è quella di limitarsi alle interfacce base offerte dal .NET: IList, ICollection, IQueryable, IEnumerable o IReadOnlyList. In realtà la regola “accettare il minimo che si può, ma restituire il massimo” pone alcuni vincoli.

IEnumerable e IReadOnlyList sono in generale le interfacce utilizzate in questi contesti. E' interessante notare che IReadOnlyList non è molto diversa da IEnumerable ma permette di essere più espliciti nella dichiarazione della propria funzione. In realtà IReadOnlyList deriva sia da IEnumerable sia da IReadOnlyCollection ed espone sia Count sia Item(int index).

Ritornare un IEnumerable permette di utilizzare yield in fase di creazione, inoltre espone un insieme non modificabile. In realtà, se l’enumerazione deriva direttamente dal sorgente dati, è possibile fare un cast dell’oggetto. Inoltre, se si ritorna direttamente il sorgente dati, bisogna fare attenzione agli eventuali aggiornamenti che invalidano l’enumerazione. Di alternative non ve ne sono molte: o si crea un nuovo insieme (magari con un semplice ToArray()) oppure si utilizzano dei lock.

Comunque, utilizzare IEnumerable sul sorgente effettivo permette di evitare duplicazione dei dati, tipico delle liste e degli array, e al chiamate di richiedere solo i dati che gli servono interrompendo la richiesta appena possibile. Questa valutazione lazy funziona in particolare con yield:

Vale poi la pena di sottolineare la differenza tra ICollection e IList: la prima permettere di azzerare l'insieme, aggiungere un elemento in coda oppure di eliminarne uno specifico. L'altra permette inserimenti e cancellazioni anche indicando la posizione dell'elemento.

AddRange non è invece implementato in nessuna delle due interfacce anche se alle volte risulta comodo, ma un'interfaccia deve essere la più semplice possibile ed è facile creare un’estensione:

In tutti i casi parlo delle versioni generiche, perché è sempre il caso di ritornare collezioni tipizzate e con il tipo più alto possibile, liberando il client dalle verifiche sul risultato.

IQueryable è l'interfaccia fondamentale per l'uso di LINQ e permette ad un query provider di eseguire lato server il cosiddetto albero delle espressioni. Questo non è un vincolo dell’interfaccia ma la tipica implementazione per i sorgenti dati, come quelli esposti tramite Entity Framework che traduce la query LINQ in comandi specifici per l’effettivo data storage utilizzato.

Su MSDN è presente una pagina dedicata alle linee guide per le collection:

  • Ritornare collection tipizzate, possibilmente con il tipo più forte possibile (quindi non uno dei tipi base).
  • Non ritornare ArrayList o List nelle API perché sono ottimizzate e pensate per un uso interno.
  • Non ritornare Hashtable o Dictionary, ma eventualmente IDictionary.
  • Sullo stesso tipo non implementare sia IEnumerator sia IEnumerable.
  • Per insiemi read/write, preferire Collection o derivate per i valori di ritorno.
  • Per insiemi read-only preferire ReadOnlyCollection.
  • Considerare di creare collezioni personalizzate per una maggiore estendibilità (da derivare dai tipi base) o per poterne cambiare l’implementazione in futuro.
  • Considerare KeyedCollection per insiemi con chiave univoca.

La regola generale è quella di richiedere, come parametri, il tipo più debole che si ha bisogno e di ritornare il tipo più forte che si ha. Ad esempio, si dovrebbe accettare un IEnumerable e ritornare una Collection. Si dice: permissivi in entrata, specifici in uscita.

Infine, è buona norma non ritornare null ma piuttosto un insieme vuoto. Il chiamante deve poi usare Any() per verificare se vi sono elementi e non il più costoso Count() == 0.

Tag: C#