CodeGym /Corsi /C# SELF /Contratto per le collezioni:

Contratto per le collezioni: ICollection<T>

C# SELF
Livello 28 , Lezione 2
Disponibile

1. Introduzione

Se con IEnumerable<T> potevamo solo girare tra gli elementi già presenti nella collezione, l'interfaccia ICollection<T> è il tuo pass per il mondo della gestione delle collezioni: puoi aggiungere, rimuovere, controllare il numero di elementi, copiarli in un array e persino monitorare i cambiamenti (sì, quasi come nella vita reale quando cerchi di tenere sotto controllo il contenuto del tuo carrello della spesa).

ICollection<T> è implementata da tutte le collezioni mutabili di .NET, come List<T>, HashSet<T>, Dictionary<TKey, TValue>.ValueCollection e anche da quelle meno popolari come Queue<T>, Stack<T> (sì, anche le code e gli stack!). È il contratto base per tutte le classi di collezioni che permettono di modificare il proprio contenuto.

Famiglia di interfacce delle collezioni


graph TD
    A[IEnumerable<T>]
    B[ICollection<T>]
    C[IList<T>]
    D[IDictionary<TKey, TValue>]
    E[Queue<T>, Stack<T>, List<T>, HashSet<T>...]

    A --> B
    B --> C
    B --> D
    B --> E
ICollection<T> estende IEnumerable<T> ed è l'interfaccia base per la maggior parte delle collezioni.

2. Cosa include l'interfaccia ICollection<T>?

A differenza di IEnumerable<T>, che serve solo per l'iterazione (foreach), ICollection<T> è come un coltellino svizzero tra le interfacce: c'è un metodo per ogni esigenza!

Ecco il suo contenuto principale:


public interface ICollection<T> : IEnumerable<T>
{
    int Count { get; }
    bool IsReadOnly { get; }
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);
}

Lista di metodi e proprietà:

  • Count — numero di elementi nella collezione. Tipo la tua funzione di conteggio preferita.
  • IsReadOnly — si può modificare la collezione? Se true, la collezione è di sola lettura (ad esempio, per alcuni wrapper).
  • Add(T item) — aggiunge un elemento alla collezione. Uno dei tuoi strumenti principali!
  • Clear() — elimina tutti gli elementi. Pulizia totale.
  • Contains(T item) — controllo veloce: c'è questo elemento nella collezione?
  • CopyTo(T[] array, int arrayIndex) — copia gli elementi in un array normale, a partire da un certo indice. Utile per passare dati a vecchie API o metodi "legacy".
  • Remove(T item) — rimuove un elemento (se presente).

3. Problemi reali da programmatore

Perché è importante capire come funziona questa interfaccia? Nella pratica scrivi spesso codice generico che lavora con qualsiasi collezione. Per esempio, una funzione può accettare una lista, una coda o un set — e non importa quale tipo concreto sia stato passato. Finché quella collezione implementa ICollection<T>, puoi aggiungere, rimuovere elementi, controllare la dimensione e persino copiare in un array. È la base per librerie flessibili e algoritmi universali.

Esempio dalla vita reale: l'interfaccia ICollection<T> si trova spesso nei parametri dei metodi e nelle proprietà delle API che restituiscono una collezione modificabile. Ad esempio, nei famosi ORM (Entity Framework, Dapper) le proprietà di tipo ICollection<T> vengono usate per descrivere collezioni di oggetti collegati.

Esempi di utilizzo

Proviamo a scrivere un metodo universale che accetta qualsiasi collezione e ci aggiunge un elemento. L'unica condizione — deve implementare ICollection<T>.


// Metodo universale per aggiungere un elemento
void AppendItem<T>(ICollection<T> collection, T item)
{
    collection.Add(item);
}

Ora puoi usare questo metodo sia con List<T>, sia con HashSet<T>, e persino con la tua implementazione personalizzata dell'interfaccia! Ecco come si usa:


var numbers = new List<int> { 1, 2, 3 };
AppendItem(numbers, 42);

var uniqueNames = new HashSet<string> { "Alice", "Bob" };
AppendItem(uniqueNames, "Charlie");

Fa quasi ridere — non ci interessa se davanti abbiamo una lista o un set, l'importante è che sia una ICollection<T>. Non sorprenderti, così sono fatti spesso i metodi universali dentro .NET!

4. Relazione con altre collezioni

ICollection<T> non è solo un contratto teoricamente elegante, ma anche la base reale per praticamente tutte le collezioni che incontrerai in .NET.

Per vedere nella pratica quanto sia universale, prova questo codice:


void ShowCollectionInfo<T>(ICollection<T> collection)
{
    Console.WriteLine($"Numero di elementi: {collection.Count}");
    Console.WriteLine($"Si può modificare: {!collection.IsReadOnly}");

    Console.WriteLine("Elementi:");
    foreach (var item in collection)
    {
        Console.WriteLine(item);
    }
}

// Per List<T>
var list = new List<string> { "mela", "arancia" };
ShowCollectionInfo(list);

// Per HashSet<T>
var set = new HashSet<string> { "mela", "arancia" };
ShowCollectionInfo(set);

Il risultato sarà corretto per qualsiasi tipo di collezione che implementa questa interfaccia. Prova con una coda, uno stack, persino con un array wrappato!

5. Collezioni di sola lettura

L'interfaccia ICollection<T> contiene la proprietà IsReadOnly, che indica se la collezione può essere modificata. Non confonderla con la proprietà degli array! Se trovi una collezione dove IsReadOnly == true, provare ad aggiungere o rimuovere un elemento lancerà un'eccezione.

Esempio:


// Creiamo un array e lo wrappiamo in una ReadOnlyCollection
var array = new int[] { 1, 2, 3 };
ICollection<int> readOnly = Array.AsReadOnly(array);

// Proviamo ad aggiungere un elemento
try
{
    readOnly.Add(4); // Verrà lanciata NotSupportedException
}
catch (NotSupportedException)
{
    Console.WriteLine("Non si possono aggiungere elementi: collezione di sola lettura!");
}

Succede, ad esempio, quando ricevi una collezione da una fonte esterna e non puoi modificarla — ma puoi leggere gli elementi e sapere quanti sono.

6. Tabella di confronto dei metodi delle collezioni più popolari secondo ICollection<T>

Collezione Add() Remove() Contains() Clear() CopyTo() IsReadOnly
List<T>
No
HashSet<T>
Sì* No
Queue<T> / Stack<T>
No/No No/No No/No Sì/Sì Sì/Sì No
ReadOnlyCollection<T>
No No No
Dictionary<TKey, TValue>.ValueCollection
No No No Sì/No*

Per HashSet<T> il metodo Add restituisce true se l'elemento è stato aggiunto, altrimenti false.

7. CopyTo — a cosa può servire

Il metodo CopyTo permette di trasferire velocemente il contenuto della collezione in un array normale. Può essere utile, ad esempio, se devi restituire i dati a una funzione di una vecchia API che accetta solo array, o fare un'elaborazione di massa usando gli array.


var contactsArray = new Contact[book.Count];
((ICollection<Contact>)book).CopyTo(contactsArray, 0);
// Ora puoi usare contactsArray alla vecchia maniera

8. Esempio

Nel nostro progetto console didattico, supponiamo di aver iniziato a sviluppare una rubrica. Abbiamo una classe Contact e la rubrica può essere implementata su qualsiasi collezione, basta che supporti aggiunta, rimozione e iterazione.


public class Contact
{
    public string Name { get; set; }
    public string Phone { get; set; }
}

public class AddressBook
{
    private readonly ICollection<Contact> _contacts;

    public AddressBook(ICollection<Contact> contacts)
    {
        _contacts = contacts;
    }

    public void AddContact(Contact contact)
    {
        _contacts.Add(contact);
    }

    public bool RemoveContact(Contact contact)
    {
        return _contacts.Remove(contact);
    }

    public int Count => _contacts.Count;

    public void PrintAll()
    {
        foreach (var contact in _contacts)
        {
            Console.WriteLine($"{contact.Name} — {contact.Phone}");
        }
    }
}

Ora la nostra rubrica può funzionare sia con List<Contact>, sia con HashSet<Contact>, e anche — in futuro — con una collezione personalizzata che implementeremo su database o file! Esempio d'uso:


var book = new AddressBook(new List<Contact>());
book.AddContact(new Contact { Name = "Ivan", Phone = "+7-123-456" });
book.AddContact(new Contact { Name = "Maria", Phone = "+7-987-654" });

Console.WriteLine($"Totale contatti: {book.Count}");
book.PrintAll();

Se vogliamo evitare duplicati, basta passare un set invece di una lista a AddressBook:


var book = new AddressBook(new HashSet<Contact>());

(serve che Contact implementi correttamente l'uguaglianza, ma di questo parleremo in un'altra lezione!)

9. Caratteristiche dei metodi ed errori tipici

Attenzione subito: non tutte le collezioni che implementano ICollection<T> si comportano allo stesso modo! Ad esempio, provare a modificare una collezione quando IsReadOnly == true lancerà un'eccezione. E se lavori con HashSet<T>, il metodo Add restituisce true solo se l'elemento è stato effettivamente aggiunto (prima non c'era). Inoltre, l'implementazione dei metodi può variare in velocità tra le collezioni — ma l'interfaccia non lo specifica.

Un altro caso comune: se la collezione è usata da più thread, modificarla in un thread e iterarla in un altro può causare eccezioni. Per gli scenari multithread servono collezioni speciali (ConcurrentBag<T>, ConcurrentQueue<T> ecc. — ne parleremo più avanti).

Un'altra trappola: se hai copiato la collezione con CopyTo, poi le modifiche all'array e alla collezione saranno indipendenti. L'array è una zona di memoria separata.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION