CodeGym /Cursos /C# SELF /Interfaces na biblioteca padrão do .NET

Interfaces na biblioteca padrão do .NET

C# SELF
Nível 24 , Lição 3
Disponível

1. Classificação das interfaces principais

Se você acha que interface é só brinquedo de arquiteto e "código limpo", e que na vida real dá pra viver sem, deixa eu te surpreender: qualquer projeto sério em C# mergulha de cabeça em interfaces. Por quê? Porque praticamente cada parte da biblioteca padrão do .NET é feita em cima de interfaces! Sem elas, você não consegue nem trabalhar com coleções, nem ler arquivos, nem filtrar coleções com LINQ.

Interfaces são o fundamento do polimorfismo e da extensibilidade no .NET, e é por causa delas que todos os "blocos" do framework se encaixam.

A biblioteca padrão do .NET é literalmente recheada de interfaces. Pra não se perder nesse mar, bora ver essa classificação (claro, não é completa — não dá pra cobrir tudo!):

Categoria Interfaces Pra que servem?
Coleções
IEnumerable
,
IEnumerator
,
IList
,
ICollection
,
IDictionary
Percorrer, modificar, acessar por índice, trabalhar com pares chave-valor
Trabalho com recursos
IDisposable
Liberação de recursos (arquivos, conexões, streams)
Comparação
IComparable
,
IComparer
,
IEquatable
Ordenação, comparação, unicidade de objetos
Serialização
ISerializable
Transformar objetos em stream de bytes (e vice-versa)
LINQ e consultas
IQueryable
,
IQueryProvider
Suporte a consultas complexas (tipo pra banco de dados)
Assincronia
IAsyncEnumerable
,
IAsyncDisposable
Percorrer e liberar recursos de forma assíncrona
Eventos e notificações
INotifyPropertyChanged
,
INotifyCollectionChanged
Reagir a mudanças em propriedades/coleções
Data e hora
IFormattable
Formatação customizada de strings
Estruturas de dados
IStructuralComparable
,
IStructuralEquatable
Comparação profunda de coleções e tuplas
Streams/entrada-saída
IStream
,
IAsyncDisposable
,
IObserver
,
IObservable
Trabalhar com streams, notificações push/pull

Importante! Muitas dessas interfaces têm tipos genéricos: List<string>. Normalmente isso aparece em coleções e interfaces de coleções Int[] → List<int>. Vamos ver isso melhor daqui a uns níveis, quando a gente for estudar coleções.

2. Interfaces de coleções

IEnumerable e IEnumerator — seu passe pro foreach

Praticamente toda coleção no .NET implementa a interface IEnumerable ou até a versão genérica IEnumerable<T>. É essa interface que deixa você usar o sintaxe mágico do foreach:

List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers) // funciona porque List<int> implementa IEnumerable<int>
{
    Console.WriteLine(n);
}

Olha como essa interface é na versão mais simples:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

IEnumerator é a interface do "iterador" mesmo, o cara que anda pela sua coleção:

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

Na vida real, você vai muito usar parâmetros e retornar valores do tipo IEnumerable<T>. Por exemplo, um método que retorna todos os números pares de um array:

public IEnumerable<int> GetEvenNumbers(int[] array)
{
    foreach (var x in array)
        if (x % 2 == 0) yield return x;
}

Sim, a palavra-chave yield faz a mágica — implementa a interface pra você, mas isso é papo pra depois.

ICollection e IList — coleções com acesso por índice e modificação

Se você quer não só percorrer, mas também adicionar/remover itens ou acessar por índice, precisa de interfaces mais específicas:

  • ICollection<T> — adiciona métodos Add, Remove e a propriedade Count.
  • IList<T> — amplia as operações, incluindo acesso por índice (this[int index]).
public void PrintFirstItem(IList<string> list)
{
    if (list.Count > 0)
        Console.WriteLine(list[0]);
}

List<T> implementa IEnumerable<T>, ICollection<T> e IList<T>. Isso é muito prático — você pode usar como lista simples ou aproveitar todos os métodos.

IDictionary<TKey, TValue> — pares "chave-valor"

Se você curte trabalhar com dicionários (e isso é bem comum!), use a interface IDictionary<TKey, TValue>. Ela garante que você pode pegar o valor pela chave e vice-versa.

public void PrintAllPairs(IDictionary<string, int> ages)
{
    foreach (var pair in ages)
        Console.WriteLine($"{pair.Key}: {pair.Value}");
}

Aqui ages pode ser qualquer coisa, tipo Dictionary<string, int> ou SortedDictionary<string, int> — o importante é que implementam essa interface!

3. Interface IDisposable: uso correto de recursos

Toda entrada/saída, manipulação de arquivos, conexões de rede, banco de dados no .NET gira em torno da interface IDisposable. Essa interface define um contrato essencial: se o objeto tem recursos não gerenciados, ele precisa ser "limpo" depois de usar. Sim, é ela que permite o sintaxe using:

using (StreamReader reader = new StreamReader("file.txt"))
{
    // Trabalha com o arquivo, e depois do using o arquivo fecha garantido!
    string line = reader.ReadLine();
}

Como é a interface? Simples demais:

public interface IDisposable
{
    void Dispose();
}

Mas toda biblioteca "séria" implementa ela! Pra saber mais sobre boas práticas com essa interface, dá uma olhada na documentação oficial.

4. Interfaces pra comparação e unicidade

IComparable<T> e IComparer<T>

Se você quer que sua coleção de objetos possa ser ordenada, os objetos precisam saber se comparar. Pra isso existe a interface IComparable<T>:

public class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Score { get; set; }

    public int CompareTo(Student? other)
    {
        // Ordena por pontuação decrescente
        if (other == null) return 1;
        return other.Score.CompareTo(this.Score);
    }
}

// Agora dá pra ordenar estudantes:
var students = new List<Student> { ... };
students.Sort();

Mais detalhes na documentação da Microsoft.

IComparer<T> deixa você definir a comparação fora da classe — tipo, às vezes você quer ordenar estudantes por nome, às vezes por nota:

public class NameComparer : IComparer<Student>
{
    public int Compare(Student? x, Student? y)
    {
        return string.Compare(x?.Name, y?.Name);
    }
}

IEquatable<T> — comparação de igualdade

Quer que seu objeto funcione em HashSet<T> (ou seja, seja "único" nas coleções)? Implemente IEquatable<T>:

public class Person : IEquatable<Person>
{
    public string Name { get; set; }
    public bool Equals(Person? other)
    {
        if (other == null) return false;
        return this.Name == other.Name;
    }
}

5. Interfaces pra eventos e notificações

Já quis que seu programa "soubesse" quando uma propriedade de um objeto mudou? Tipo, atualizar a interface automaticamente quando o usuário muda o sobrenome? Pra isso existe a interface INotifyPropertyChanged. Ela é super usada em apps com interface gráfica (tipo WPF ou Xamarin).

A assinatura da interface é simples:

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler? PropertyChanged;
}

Exemplo de implementação:

public class User : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    private string name;
    public string Name
    {
        get => name;
        set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }
}

Agora qualquer binding da interface com esse objeto vai atualizar automaticamente quando Name mudar. Se quiser saber mais — confere a documentação oficial.

6. Outras interfaces úteis

Interface IFormattable: formatação flexível

Quando você quer que seu objeto seja exibido bonito e de vários jeitos em string (tipo com diferentes casas decimais), implemente a interface IFormattable:

public class Temperature : IFormattable
{
    public double Celsius { get; }
    public Temperature(double celsius) => Celsius = celsius;

    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        // Sem complicar, só mostra 'C' ou 'F' dependendo do formato
        if (format == "F")
            return $"{Celsius * 9 / 5 + 32} F";
        return $"{Celsius} C";
    }
}

Agora você pode chamar temp.ToString("F", null) ou temp.ToString("C", null). É por isso que DateTime, double, decimal e outros tipos nativos suportam formatação flexível.

Interfaces pra assincronia

Com a chegada da assincronia no C#, surgiram novas interfaces pra esse mundo. Por exemplo, se seu objeto libera recursos de forma assíncrona — implemente IAsyncDisposable:

public interface IAsyncDisposable
{
    ValueTask DisposeAsync();
}

E agora, em vez de using, você escreve await using!

Com enumerações assíncronas (await foreach) você usa a interface IAsyncEnumerable<T>, que permite percorrer elementos sem travar a thread principal. Isso é útil, por exemplo, pra ler arquivos grandes em partes ou trabalhar com streams de dados da internet. Mais sobre isso no nível 58 :P

Interfaces de serialização: ISerializable

Se sua missão é transformar objetos em stream de bytes pra salvar/enviar (tipo pela rede), o .NET tem a interface ISerializable. Hoje em dia ela é menos usada, porque existem jeitos mais práticos (tipo atributos e serializadores prontos), mas vale citar. Olha a assinatura:

public interface ISerializable
{
    void GetObjectData(SerializationInfo info, StreamingContext context);
}

7. Usando interfaces na prática: app de verdade

Imagina que você tá fazendo um app de console pra gerenciar livros na biblioteca (ué, alguém tem que fazer!). Quer poder listar livros, ordenar, filtrar, carregar e salvar dados. Sabendo interfaces do .NET, você consegue:

  • Retornar vários tipos de coleção usando IEnumerable<Book>, sem o usuário precisar saber se é array ou lista.
  • Adicionar ordenação por vários critérios: implementa IComparable<Book> pra ordenar por título e um IComparer<Book> separado pra autor.
  • Garantir liberação eficiente de recursos ao trabalhar com arquivos usando IDisposable.
  • Filtrar livros por gênero ou autor usando LINQ, que funciona com tudo que implementa IEnumerable<T>.
  • Escalar o app — tipo trocar o armazenamento de arquivos por banco de dados, se suas classes usam interfaces (IBookStorage) e não implementações concretas.

8. Erros comuns e particularidades

Um dos erros mais comuns de quem tá começando é usar implementações concretas ("List") em vez de interfaces ("IEnumerable", "IList") ao declarar variáveis e argumentos de métodos. LEMBRE-SE: se você "programa em interfaces", seu código fica flexível e fácil de expandir.

Às vezes, tem que prestar atenção em qual nível de interface usar — tipo, se seu método só precisa percorrer, use IEnumerable<T> e não IList<T>, pra não exigir coisa desnecessária.

Outro detalhe: se sua classe implementa várias interfaces e elas têm métodos ou propriedades com o mesmo nome, vai ter que usar implementação explícita de interface (explicit implementation), senão o compilador não entende nada.

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