Menu

Interface em C#: declarar, implementar, métodos padrão e exemplos

Como as interfaces funcionam em C#: declarar uma, implementá-la em classes e structs, usar a interface como tipo, implementar várias ao mesmo tempo, implementação explícita, as interfaces do framework que você mais vai implementar (IComparable<T>, IEnumerable<T>, IDisposable) e os métodos padrão de interface do C# 8.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Uma interface é um contrato: uma lista com nome de membros que um tipo promete fornecer. Ela diz o que um tipo sabe fazer e nada sobre como. Classes e structs implementam interfaces, e o código escrito para a interface funciona com todas as implementações, inclusive as escritas depois.

Declarando e implementando uma interface

Uma interface declara membros sem corpo. Por convenção, o nome começa com I. Uma classe a implementa listando-a depois de dois pontos e fornecendo um membro público para cada um:

Saída:

15% off: 68
10 off: 70
Best for 80: 68
Best for 40: 30

BestPrice não sabe nada de PercentOff nem de FixedOff. Ele só depende de IDiscount, então adicionar depois uma classe BuyOneGetOne não exige nenhuma mudança nele. Esse desacoplamento é o motivo de as interfaces existirem.

Algumas regras sobre a declaração:

  • Os membros de interface são públicos por padrão, e os membros que os implementam também precisam ser públicos (a menos que sejam implementados explicitamente, abaixo).
  • Uma interface pode declarar métodos, propriedades, indexadores e eventos. Ela não pode declarar campos de instância nem construtores.
  • Uma classe que deixa de fora um membro não compila: erro CS0535, "'FixedOff' does not implement interface member 'IDiscount.Apply(decimal)'".
  • Você não pode criar uma interface com new. Você cria uma classe que a implementa e pode guardá-la em uma variável do tipo da interface: IDiscount d = new FixedOff(5);.

A interface como tipo

Uma variável, um parâmetro, um campo ou um tipo de retorno pode ser declarado como uma interface. Ele passa a aceitar qualquer objeto que a implemente, e só expõe os membros da interface. É assim que você escreve código que não se importa com a implementação que recebe, e é assim que os testes trocam por uma versão falsa:

public class Checkout
{
    private readonly IPaymentGateway gateway;          // not StripeGateway, not PayPalGateway
    public Checkout(IPaymentGateway gateway) { this.gateway = gateway; }

    public bool Pay(decimal amount) => gateway.Charge(amount);
}

// Production: new Checkout(new StripeGateway(apiKey))
// Unit test:  new Checkout(new FakeGateway(alwaysSucceeds: true))

Os contêineres de injeção de dependência do ASP.NET Core são construídos sobre isso: os serviços são registrados e pedidos pela interface.

Implementando várias interfaces

Uma classe tem no máximo uma classe base, mas pode implementar qualquer número de interfaces. Primeiro a classe base, depois as interfaces, separadas por vírgulas:

Saída:

INVOICE Studio rent March: 950.00
True
True

Interfaces também podem herdar de outras interfaces: interface IRepository<T> : IReadRepository<T> acrescenta membros aos que herda, e uma classe que implementa IRepository<T> precisa fornecer os dois conjuntos.

Implementação explícita de interface

Às vezes duas interfaces declaram um membro com o mesmo nome e significados diferentes, ou um membro de interface não faz sentido na superfície pública da classe. Implemente-o explicitamente, prefixando o nome da interface e deixando de fora o modificador de acesso:

Saída:

Desk lamp,34.90
Desk lamp for 34.90

Um membro implementado explicitamente só é alcançável pela interface. O framework usa isso para tirar ruído de tipos comuns: arrays implementam IList.Add explicitamente (ele lança NotSupportedException, já que arrays têm tamanho fixo), então o método não aparece quando você digita array..

Interfaces do framework que você vai implementar

Implementar uma interface padrão encaixa o seu tipo no código já existente do framework. Três aparecem o tempo todo.

IComparable<T> dá a um tipo uma ordem natural, que List<T>.Sort(), Array.Sort e Max() usam. CompareTo retorna um número negativo, zero ou um número positivo:

Saída:

1.4, 2.9, 2.10, 10.0

Ordenar as mesmas versões como strings daria 1.4, 10.0, 2.10, 2.9. Sem IComparable<T>, Sort() lança InvalidOperationException porque não tem como comparar dois objetos Version.

IEnumerable<T> torna um tipo utilizável no foreach e com LINQ. A forma fácil de implementar GetEnumerator é com yield return, assunto da página de IEnumerable e yield.

IDisposable marca um tipo que guarda algo que precisa ser liberado (um handle de arquivo, uma conexão, um timer). O único método dela, Dispose, é o que a instrução using chama quando o bloco termina, mesmo que uma exceção seja lançada:

Saída:

open sales.txt
  write to sales.txt: March total: 12400
close sales.txt
after using

Métodos padrão de interface (C# 8)

Adicionar um membro a uma interface publicada costumava quebrar todas as classes que a implementavam. Desde o C# 8, um membro de interface pode ter corpo, que as classes que a implementam herdam, a menos que forneçam o seu:

public interface ILogger
{
    void Write(string message);

    // New in version 2 of the library. Existing implementers keep compiling.
    void Error(string message) => Write("ERROR: " + message);
}

public class ConsoleLogger : ILogger
{
    public void Write(string message) => Console.WriteLine(message);
}

ILogger log = new ConsoleLogger();
log.Error("disk full");            // ERROR: disk full

var direct = new ConsoleLogger();
// direct.Error("x");              // does not compile: the default method belongs to the interface

A última linha é a parte que surpreende: um método padrão não é herdado como membro próprio da classe, então só pode ser chamado pelo tipo da interface. Métodos padrão também não podem usar campos de instância (as interfaces não têm nenhum); eles trabalham por meio dos outros membros da interface. Eles exigem .NET Core 3.0 ou posterior e não estão disponíveis no .NET Framework.

O C# 8 também permitiu membros static em interfaces, e o C# 11 adicionou os membros static abstract, que permitem que código genérico chame um método static ou um operador em T. É sobre isso que a matemática genérica do .NET 7 é construída (INumber<T>, where T : INumber<T>).

Interface ou classe abstrata?

Use uma interface para uma capacidade que tipos sem relação podem compartilhar, quando structs devem participar, ou quando uma classe precisa de vários desses papéis. Use uma classe abstrata quando as implementações são variações de uma mesma coisa e compartilham estado ou um algoritmo fixo. A página de classes abstratas tem uma tabela lado a lado. Muitos designs usam as duas: uma interface para quem consome, e uma classe base abstrata que implementa as partes chatas para quem implementa.

Erros comuns

  • Implementação ausente ou não pública. Todo membro da interface precisa de um membro público com assinatura correspondente, ou de uma implementação explícita. Senão é CS0535 (ou CS0737 quando o método existe, mas não é público).
  • Interfaces com uma implementação só "por via das dúvidas". Uma interface vale a pena quando há várias implementações ou uma versão falsa para testes. Senão, é mais um arquivo para navegar.
  • Interfaces gordas. Uma interface com vinte membros obriga todo implementador a escrever vinte membros. Divida-a em papéis menores (IReader, IWriter) que as classes combinam.
  • Fazer cast de uma interface de volta para uma classe. ((StripeGateway)gateway).Refund() desfaz o desacoplamento. Se quem chama precisa de Refund, ele pertence à interface.
  • Esperar chamar um método padrão de interface pela classe. Ele é acessado pelo tipo da interface.

Perguntas frequentes

O que é uma interface em C#?

Uma interface é um conjunto com nome de membros (métodos, propriedades, eventos, indexadores) que um tipo promete fornecer, sem estado de instância. interface IDiscount { decimal Apply(decimal price); } diz "tudo o que é um IDiscount sabe se aplicar a um preço". Classes e structs a implementam com dois pontos, e o código pode então trabalhar com qualquer uma delas por meio do tipo da interface.

Como implementar uma interface em C#?

Liste-a depois de dois pontos na declaração da classe (depois da classe base, se houver uma) e forneça um membro público para cada membro da interface: class HolidayDiscount : IDiscount { public decimal Apply(decimal price) => price * 0.9m; }. Um membro ausente é o erro CS0535. O Visual Studio e o Rider conseguem gerar os esboços com uma correção rápida.

Uma classe em C# pode implementar várias interfaces?

Sim, quantas precisar, separadas por vírgulas: class Invoice : IPrintable, IComparable<Invoice>, IDisposable. É a resposta de C# para a herança múltipla: uma classe tem uma classe base, mas pode ter muitos papéis. Se duas interfaces declaram o mesmo membro, um único método público pode atender às duas, ou você pode usar a implementação explícita para dar a cada uma o seu.

O que é implementação explícita de interface em C#?

Implementar um membro com o nome da interface na frente e sem modificador de acesso: string IExportable.Format() { ... }. O membro então só pode ser chamado por meio de uma variável do tipo da interface, não pela classe. Ela é usada para resolver conflitos de nomes entre duas interfaces e para manter membros de interface pouco usados fora da superfície pública da classe.

O que são métodos padrão de interface em C#?

Desde o C# 8, um membro de interface pode ter corpo: void Error(string m) => Write("ERROR: " + m);. As classes que implementam a interface o recebem de graça e podem fornecer o seu. O recurso existe para que autores de bibliotecas possam adicionar membros a uma interface publicada sem quebrar todos que a implementam. Um método padrão é acessado pelo tipo da interface, não pela classe.

Por que os nomes de interfaces em C# começam com I?

É a convenção de nomes do .NET (IEnumerable, IDisposable, IComparable<T>): um I maiúsculo seguido de um nome em PascalCase, muitas vezes um adjetivo terminado em "-able". O compilador não exige isso, mas segui-la torna os tipos de interface reconhecíveis de relance, principalmente em uma declaração de classe em que a classe base e as interfaces dividem uma mesma lista.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR