CodeGym /Cursos /C# SELF /Contrato de coleção: ICollection<T>

Contrato de coleção: ICollection<T>

C# SELF
Nível 28, Lição 2
Disponível

1. Introdução

Se com IEnumerable<T> a gente só podia passear pelo conteúdo já existente da coleção, a interface ICollection<T> é tipo seu passe VIP pro mundo do controle de coleções: dá pra adicionar, remover, checar quantos itens tem, copiar pra array e até monitorar mudanças (tipo quando você tenta controlar o que tem no seu carrinho de compras).

ICollection<T> é implementada por todas as coleções mutáveis do .NET, tipo List<T>, HashSet<T>, Dictionary<TKey, TValue>.ValueCollection e até as menos famosas como Queue<T>, Stack<T> (sim, até filas e pilhas!). É o contrato básico pra todas as classes de coleção que deixam você mexer no conteúdo.

Família de interfaces de coleções


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> e é a interface base pra maioria das coleções.

2. O que tem na interface ICollection<T>?

Diferente de IEnumerable<T>, que só serve pra percorrer (foreach), ICollection<T> é tipo um canivete suíço das interfaces: tem método pra tudo!

Olha só o que ela tem de principal:


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 e propriedades:

  • Count — quantidade de elementos na coleção. Tipo aquela função de contar que você curte.
  • IsReadOnly — dá pra modificar a coleção? Se true, é só leitura (tipo alguns wrappers).
  • Add(T item) — adiciona um item na coleção. Ferramenta essencial!
  • Clear() — remove tudo. Limpeza total.
  • Contains(T item) — checagem rápida: esse item tá na coleção?
  • CopyTo(T[] array, int arrayIndex) — copia os itens pra um array normal, começando de um índice. Útil pra passar dados pra APIs antigas ou métodos "velhos".
  • Remove(T item) — remove o item (se ele estiver lá).

3. Desafios reais de programador

Por que é importante entender como essa interface funciona? Na prática, você vai escrever muito código genérico que trabalha com qualquer coleção. Por exemplo, uma função pode receber uma lista, fila ou conjunto — e tanto faz o tipo exato. Se a coleção implementa ICollection<T>, você pode adicionar, remover itens, checar o tamanho e até copiar pra array. Isso é a base pra libs flexíveis e algoritmos genéricos.

Exemplo da vida real: a interface ICollection<T> aparece direto nos parâmetros de métodos e propriedades de APIs que devolvem coleções pra você modificar. Por exemplo, nos ORMs famosos (Entity Framework, Dapper), propriedades do tipo ICollection<T> são usadas pra descrever coleções de objetos relacionados.

Exemplos de uso

Bora criar um método genérico que recebe qualquer coleção e adiciona um item nela. Só precisa implementar ICollection<T>.


// Método genérico pra adicionar item
void AppendItem<T>(ICollection<T> collection, T item)
{
    collection.Add(item);
}

Agora dá pra usar esse método tanto com List<T>, quanto com HashSet<T>, ou até com sua própria implementação! Olha só como fica:


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

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

É até engraçado — não importa se é lista ou conjunto, o que interessa é ser ICollection<T>. Não se espante, métodos genéricos assim são super comuns dentro do .NET!

4. Ligação com outras coleções

ICollection<T> não é só um contrato bonito na teoria, mas a base real de praticamente todas as coleções que você vai ver no .NET.

Pra ver na prática como ela é versátil, tenta esse código:


void ShowCollectionInfo<T>(ICollection<T> collection)
{
    Console.WriteLine($"Quantidade de itens: {collection.Count}");
    Console.WriteLine($"Pode modificar: {!collection.IsReadOnly}");

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

// Pra List<T>
var list = new List<string> { "maçã", "laranja" };
ShowCollectionInfo(list);

// Pra HashSet<T>
var set = new HashSet<string> { "maçã", "laranja" };
ShowCollectionInfo(set);

O resultado vai ser certinho pra qualquer tipo de coleção que implemente essa interface. Testa com fila, pilha, até com array envelopado!

5. Coleções somente leitura

A interface ICollection<T> tem a propriedade IsReadOnly, que diz se dá pra modificar a coleção. Não confunde com a propriedade dos arrays! Se você pegar uma coleção onde IsReadOnly == true, tentar adicionar ou remover item vai dar exceção.

Exemplo:


// Vamos criar um array e envolver em ReadOnlyCollection
var array = new int[] { 1, 2, 3 };
ICollection<int> readOnly = Array.AsReadOnly(array);

// Tenta adicionar um item
try
{
    readOnly.Add(4); // Vai lançar NotSupportedException
}
catch (NotSupportedException)
{
    Console.WriteLine("Não dá pra adicionar: coleção só leitura!");
}

Isso acontece, por exemplo, quando você recebe uma coleção de fora e não pode mexer — mas pode ler os itens e saber quantos tem.

6. Tabela comparativa dos métodos das coleções populares via ICollection<T>

Coleção Add() Remove() Contains() Clear() CopyTo() IsReadOnly
List<T>
Sim Sim Sim Sim Sim Não
HashSet<T>
Sim* Sim Sim Sim Sim Não
Queue<T> / Stack<T>
Não/Não Não/Não Não/Não Sim/Sim Sim/Sim Não
ReadOnlyCollection<T>
Não Não Sim Não Sim Sim
Dictionary<TKey, TValue>.ValueCollection
Não Não Sim Não Sim Sim/Não*

Pra HashSet<T> o método Add retorna true se o item foi adicionado, senão false.

7. CopyTo — pra que serve?

O método CopyTo deixa você transferir rapidinho o conteúdo da coleção pra um array normal. Isso é útil se, por exemplo, você precisa devolver dados pra uma função de API antiga que só aceita arrays, ou quer processar tudo de uma vez usando array.


var contactsArray = new Contact[book.Count];
((ICollection<Contact>)book).CopyTo(contactsArray, 0);
// Agora dá pra usar contactsArray do jeito antigo

8. Exemplo

No nosso app de console de estudos, digamos que começamos a criar uma agenda de contatos. Temos a classe Contact, e a agenda pode ser baseada em qualquer coleção, desde que dê pra adicionar, remover e percorrer.


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

Agora nossa agenda pode funcionar tanto com List<Contact>, quanto com HashSet<Contact>, ou até — no futuro — com uma coleção própria baseada em banco de dados ou arquivos! Exemplo de 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($"Total de contatos: {book.Count}");
book.PrintAll();

Se quiser evitar duplicados, é só passar um conjunto pra AddressBook em vez de lista:


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

(precisa que Contact implemente comparação direitinho, mas isso é papo pra outra aula!)

9. Particularidades dos métodos e erros comuns

Já fica ligado: nem toda coleção que implementa ICollection<T> se comporta igual! Por exemplo, tentar modificar uma coleção quando IsReadOnly == true vai dar exceção. E se você usar HashSet<T>, o método Add só retorna true se o item realmente foi adicionado (não existia antes). Além disso, cada coleção pode ter performance diferente nos métodos — mas isso não é garantido pela interface.

Outro detalhe comum: se a coleção for acessada por várias threads, modificar em uma e percorrer em outra pode dar exceção. Pra cenários multi-thread, existem coleções especiais (ConcurrentBag<T>, ConcurrentQueue<T> e outras — falamos disso depois).

Mais uma pegadinha: se você copiar a coleção com CopyTo, depois as mudanças no array e na coleção ficam independentes. O array é uma área de memória separada.

2
Tarefa
C# SELF, nível 28, lição 2
Bloqueado
Criação e modificação de uma coleção usando ICollection
Criação e modificação de uma coleção usando ICollection
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION