CodeGym /Cursos /C# SELF /Contrato para colecciones:

Contrato para colecciones: ICollection<T>

C# SELF
Nivel 28 , Lección 2
Disponible

1. Introducción

Si con IEnumerable<T> solo podíamos recorrer el contenido ya existente de una colección, la interfaz ICollection<T> es tu pase al mundo del control de colecciones: puedes añadir, eliminar, comprobar cuántos elementos hay, copiarlos a un array e incluso vigilar los cambios (vamos, casi como en la vida real cuando intentas controlar lo que tienes en la cesta de la compra).

ICollection<T> está implementada por todas las colecciones mutables de .NET, como List<T>, HashSet<T>, Dictionary<TKey, TValue>.ValueCollection e incluso otras menos populares como Queue<T>, Stack<T> (sí, sí, ¡hasta colas y pilas!). Es el contrato base para todas las clases de colecciones que permiten modificar su contenido.

Familia de interfaces de colecciones


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> amplía IEnumerable<T> y es la interfaz base para la mayoría de colecciones.

2. ¿Qué incluye la interfaz ICollection<T>?

A diferencia de IEnumerable<T>, que solo sirve para recorrer (foreach), ICollection<T> es como una navaja suiza entre las interfaces: ¡hay un método para cada cosa!

Esto es lo principal que trae:


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 de métodos y propiedades:

  • Count — número de elementos en la colección. Como tu función favorita de contar.
  • IsReadOnly — ¿se puede modificar la colección? Si true, la colección es solo de lectura (por ejemplo, para algunos wrappers).
  • Add(T item) — añade un elemento a la colección. ¡Una de tus herramientas principales!
  • Clear() — elimina todos los elementos. Limpieza total.
  • Contains(T item) — comprobación rápida: ¿existe ese elemento en la colección?
  • CopyTo(T[] array, int arrayIndex) — copia los elementos a un array normal, empezando desde un índice concreto. Útil para pasar datos a APIs externas o métodos "legacy".
  • Remove(T item) — elimina el elemento (si está).

3. Tareas reales de programador

¿Por qué es importante entender cómo funciona esta interfaz? En la práctica, muchas veces escribes código genérico que funciona con cualquier colección. Por ejemplo, una función puede recibir cualquier lista, cola o conjunto — y da igual el tipo concreto de colección que le pases. Mientras implemente ICollection<T>, podrás añadir, eliminar elementos, comprobar el tamaño e incluso copiar a un array. Es la base para librerías flexibles y algoritmos universales.

Ejemplo de la vida real: la interfaz ICollection<T> aparece mucho en los parámetros de métodos y propiedades de APIs que devuelven una colección para modificar. Por ejemplo, en ORMs populares (Entity Framework, Dapper) las propiedades de tipo ICollection<T> se usan para describir colecciones de objetos relacionados.

Ejemplos de uso

Vamos a escribir un método genérico que acepte cualquier colección y añada ahí un elemento. La única condición — que implemente ICollection<T>.


// Método genérico para añadir un elemento
void AppendItem<T>(ICollection<T> collection, T item)
{
    collection.Add(item);
}

Ahora puedes usar este método tanto con List<T>, como con HashSet<T>, ¡o incluso con tu propia implementación de la interfaz! Así se usa:


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

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

Hasta hace gracia — nos da igual si es una lista o un conjunto, lo importante es que sea ICollection<T>. No te sorprendas, así funcionan muchos métodos genéricos dentro de .NET.

4. Relación con otras colecciones

ICollection<T> no es solo un contrato teórico bonito, sino la base real de casi todas las colecciones que vas a ver en .NET.

Para ver en la práctica lo universal que es, prueba este código:


void ShowCollectionInfo<T>(ICollection<T> collection)
{
    Console.WriteLine($"Número de elementos: {collection.Count}");
    Console.WriteLine($"¿Se puede modificar?: {!collection.IsReadOnly}");

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

// Para List<T>
var list = new List<string> { "manzana", "naranja" };
ShowCollectionInfo(list);

// Para HashSet<T>
var set = new HashSet<string> { "manzana", "naranja" };
ShowCollectionInfo(set);

El resultado será igual de correcto para cualquier tipo de colección que implemente esta interfaz. Pruébalo con una cola, una pila, ¡incluso con un array envuelto!

5. Colecciones solo de lectura

La interfaz ICollection<T> tiene la propiedad IsReadOnly, que indica si se puede modificar la colección. ¡No lo confundas con la propiedad típica de los arrays! Si te encuentras una colección donde IsReadOnly == true, intentar añadir o eliminar un elemento lanzará una excepción.

Ejemplo:


// Creamos un array y lo envolvemos en ReadOnlyCollection
var array = new int[] { 1, 2, 3 };
ICollection<int> readOnly = Array.AsReadOnly(array);

// Intentamos añadir un elemento
try
{
    readOnly.Add(4); // Lanzará NotSupportedException
}
catch (NotSupportedException)
{
    Console.WriteLine("¡No se pueden añadir elementos: colección solo de lectura!");
}

Esto pasa, por ejemplo, cuando recibes una colección de una fuente externa y no se puede modificar — pero sí puedes leer los elementos y saber cuántos hay.

6. Tabla comparativa de métodos de colecciones populares desde la perspectiva de ICollection<T>

Colección 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*

Para HashSet<T> el método Add devuelve true si el elemento fue añadido, si no false.

7. CopyTo — ¿para qué puede servir?

El método CopyTo te permite transferir rápidamente el contenido de la colección a un array normal. Es útil, por ejemplo, si tienes que devolver datos a una función de una API antigua que solo acepta arrays, o hacer un procesamiento masivo usando arrays.


var contactsArray = new Contact[book.Count];
((ICollection<Contact>)book).CopyTo(contactsArray, 0);
// Ahora puedes usar contactsArray a la vieja usanza

8. Ejemplo

En nuestro proyecto de consola para aprender, imagina que empezamos a desarrollar una agenda de contactos. Supón que tenemos una clase Contact, y la agenda puede estar basada en cualquier colección, siempre que permita añadir, eliminar y recorrer.


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}");
        }
    }
}

Ahora nuestra agenda puede funcionar tanto con List<Contact>, como con HashSet<Contact>, ¡o incluso — en el futuro — con tu propia colección basada en base de datos o archivos! Ejemplo de uso:


var book = new AddressBook(new List<Contact>());
book.AddContact(new Contact { Name = "Iván", Phone = "+34-123-456" });
book.AddContact(new Contact { Name = "María", Phone = "+34-987-654" });

Console.WriteLine($"Total de contactos: {book.Count}");
book.PrintAll();

Si quieres evitar duplicados, simplemente pasa un conjunto en vez de una lista a AddressBook:


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

(se requiere que Contact implemente correctamente la comparación, ¡pero eso será otra lección!)

9. Particularidades de los métodos y errores típicos

Ojo: ¡no todas las colecciones que implementan ICollection<T> se comportan igual! Por ejemplo, intentar modificar una colección cuando IsReadOnly == true lanzará una excepción. Y si trabajas con HashSet<T>, el método Add solo devuelve true si el elemento realmente se añadió (antes no estaba). Además, la implementación de los métodos puede variar en velocidad según la colección — pero eso no lo define la interfaz.

Otro detalle típico: si la colección está sincronizada entre varios hilos, modificar la colección en un hilo y recorrerla en otro puede lanzar excepciones. Para escenarios multihilo necesitas colecciones especiales (ConcurrentBag<T>, ConcurrentQueue<T> y otras — de eso hablaremos más adelante).

Otra trampa: si copiaste la colección con CopyTo, después los cambios en el array y en la colección serán independientes. El array es una zona de memoria aparte.

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