CodeGym /Cursos /C# SELF /Papel das interfaces nas coleções em C#

Papel das interfaces nas coleções em C#

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

1. Por que coleções precisam de interfaces?

Vamos ser sinceros: enquanto a gente escrevia código usando um tipo específico (List<int>, Dictionary<string, string>), tudo era simples e tranquilo. Mas imagina que você resolve trocar o List<T> por um HashSet<T>, porque agora só quer valores únicos. Ou criar um método que funcione tanto com um quanto com outro tipo de coleção. E aí?

Pra criar um ponto de entrada único — você precisa de um "adaptador universal". Em C#, esse adaptador são as interfaces. Elas definem um conjunto de regras (métodos, propriedades) que uma coleção precisa ter. Tudo que implementa a interface — suporta essas operações.

É tipo carregador de celular: se tem entrada padrão USB-C, você carrega o celular, o aspirador moderno e até a escova de dente elétrica (só não vai confundir no escuro!).

Exemplo prático

Digamos que você quer criar um método que soma todos os números de qualquer coleção de números. Olha como seria sem interfaces:


// Esse método só funciona com arrays:
static int SomarArray(int[] array)
{
    int soma = 0;
    for (int i = 0; i < array.Length; i++)
    {
        soma += array[i];
    }
    return soma;
} 

// E esse - só com List<int>:
static int SomarLista(List<int> lista)
{
    int soma = 0;
    for (int i = 0; i < lista.Count; i++)
        soma += lista[i];
    return soma;
}

Chato, né? O código é quase igual. Usando a interface IEnumerable<int> dá pra fazer de forma universal:


static int SomarTudo(IEnumerable<int> colecao)
{
    int soma = 0;
    foreach (var numero in colecao)
        soma += numero;
    return soma;
}

E agora esse método aceita qualquer tipo de coleção que implemente IEnumerable<int>: array, lista, conjunto e por aí vai.

2. Principais interfaces de coleções

No .NET, as coleções têm algumas interfaces principais. Bora ver rapidinho — nas próximas aulas você vai ver os detalhes de cada uma.

Interface Pra que serve Exemplo de coleções que implementam
IEnumerable<T>
Percorrer elementos (foreach) Todas:
List<T>
,
HashSet<T>
, arrays
ICollection<T>
Conjunto mutável (Add, Remove, Count)
List<T>
,
HashSet<T>
,
Dictionary<K,V>
IList<T>
Acesso por índice, alteração
List<T>
, arrays
IDictionary<K,V>
Trabalhar com pares "chave-valor"
Dictionary<K,V>
,
SortedDictionary<K,V>
ISet<T>
Conjunto (elementos únicos)
HashSet<T>
,
SortedSet<T>

P.S. Pra facilitar, por enquanto a gente não vai falar dos "vários níveis" de herança entre essas interfaces. Quem herda de quem — isso fica pra depois.

Visualização: árvore das interfaces de coleções


           IEnumerable<T>
                 ^
                 |
          ICollection<T>
              ^       ^
              |       |
         IList<T>  ISet<T>
              |       |
         List<T>   HashSet<T>
Hierarquia das interfaces para listas e conjuntos

Para dicionários:


           IEnumerable<KeyValuePair<K,V>>
                      ^
                      |
               ICollection<KeyValuePair<K,V>>
                      ^
                      |
               IDictionary<K,V>
                      |
                Dictionary<K,V>
Hierarquia das interfaces para dicionários

3. Por que interfaces na vida real?

Flexibilidade

Quando seu método ou classe trabalha não com um tipo específico (List<int>), mas com uma interface (IEnumerable<int>), você pode passar qualquer coleção que implemente essa interface. No fim, seu programa fica flexível e fácil de expandir. (Em vez de "Só iPhone" — agora serve qualquer celular com Bluetooth.)


static void ImprimirTudo(IEnumerable<string> colecao)
{
    foreach (var item in colecao)
        Console.WriteLine(item);
}

Agora dá pra imprimir elementos de qualquer lista, array, conjunto, até resultado de consulta LINQ!

Unificação

Se você criar seu próprio tipo de coleção (sei lá, uma coleção super específica pra alguma tarefa), implemente as interfaces certas — e sua coleção vai funcionar com código de terceiros, mesmo que eles nem saibam da existência dela. Por isso que no .NET sempre falam: "programe no nível das interfaces, não das implementações concretas".

Compatibilidade com ferramentas padrão

Por suportar as interfaces padrão, suas coleções podem funcionar com LINQ, ordenação, serialização e outras paradas do .NET.

4. List<T> — é lista, coleção e enumeração ao mesmo tempo

Rapidinho sobre enumeração

IEnumerable<T> — é a interface mais importante e básica das coleções no .NET. Ela define só um método — GetEnumerator(), que retorna um enumerador pra iterar na coleção. É por causa dele que você pode escrever:


foreach (var nome in nomes)
{
    Console.WriteLine(nome);
}

Onde nomes pode ser lista, conjunto, até array. foreach é um açúcar sintático esperto que por baixo dos panos usa o método GetEnumerator().

A interface IEnumerable (sem <T>) é mais antiga, não genérica. Hoje em dia, quase sempre use a versão genérica.

Sintaxe da declaração da classe List

Olha como é a declaração da classe List<T>, se a gente der uma espiada na mágica:


public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, ...

Isso significa que o objeto List<T> pode ser usado como:

  • Lista com acesso por índice (IList<T>)
  • Coleção onde dá pra adicionar/remover elementos (ICollection<T>)
  • Simplesmente um conjunto de elementos pra percorrer (IEnumerable<T>)

Exemplo:


List<string> nomes = new List<string> { "Anna", "Boris", "Vasya" };

// Como IEnumerable - dá pra percorrer no loop
IEnumerable<string> comoEnumerable = nomes;
foreach (var n in comoEnumerable) Console.WriteLine(n);

// Como ICollection - dá pra saber o Count:
ICollection<string> comoColecao = nomes;
int quantidade = comoColecao.Count;

// Como IList - dá pra acessar pelo índice:
IList<string> comoLista = nomes;
string primeiroItem = comoLista[0];

5. Exemplo de aplicação

Digamos que a gente tem um app simples pra gerenciar usuários e seus papéis (a gente começou ele nas aulas passadas):


// Classe de usuário
public class Usuario
{
    public string Nome { get; set; }
    public int Idade { get; set; }
}

A gente pode guardar usuários em qualquer coleção que implemente IEnumerable<Usuario>:


List<Usuario> listaUsuarios = new List<Usuario> { new Usuario { Nome = "Anna", Idade = 20 } };
HashSet<Usuario> conjuntoUsuarios = new HashSet<Usuario> { new Usuario { Nome = "Boris", Idade = 25 } };

// Método que imprime os nomes de todos os usuários
static void ImprimirNomesUsuarios(IEnumerable<Usuario> usuarios)
{
    foreach (var usuario in usuarios)
        Console.WriteLine(usuario.Nome);
}

// Usando com diferentes tipos de coleção:
ImprimirNomesUsuarios(listaUsuarios);
ImprimirNomesUsuarios(conjuntoUsuarios);

Agora o app ficou muito mais flexível — sem mágica nem overloads customizados!

6. Erros comuns e pegadinhas

Um dos erros mais comuns é usar tipos muito específicos quando uma interface já seria suficiente:


// Ruim (ficou preso na implementação)
void FazerAlgoComLista(List<int> numeros) { ... }

// Melhor (funciona com qualquer coleção)
void FazerAlgoComNumeros(IEnumerable<int> numeros) { ... }

Outra armadilha — esquecer que a interface só define as operações que ela descreve. Por exemplo, em IEnumerable<T> não dá pra saber a quantidade de elementos (Count), pra isso você precisa usar ICollection<T> — ou contar manualmente.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION