A partir do C # 8.0 no .NET Core 3.0, você pode definir uma implementação ao declarar um membro de uma interface. O cenário mais comum é adicionar membros com segurança a uma interface já lançada e usada por inúmeros clientes.
Neste tutorial, você aprenderá como:
- Amplie as interfaces com segurança adicionando métodos com implementações.
- Crie implementações parametrizadas para fornecer maior flexibilidade.
- Habilite os implementadores a fornecer uma implementação mais específica na forma de uma substituição.

Pré-requisitos
Você precisará configurar sua máquina para executar o .NET Core, incluindo o compilador de visualização C # 8.0. O compilador de visualização C # 8.0 está disponível a partir do Visual Studio 2019 ou o SDK de visualização do .NET Core 3.0 mais recente. Os membros da interface padrão estão disponíveis a partir da visualização 4 do .NET Core 3.0.
Visão geral do cenário
Este tutorial começa com a versão 1 de uma biblioteca de relacionamento com o cliente. Você pode obter o aplicativo inicial em nosso repositório de amostras no GitHub . A empresa que construiu essa biblioteca pretendia que os clientes com aplicativos existentes adotassem sua biblioteca. Eles forneceram definições mínimas de interface para os usuários de sua biblioteca implementarem.
public interface ICustomer { IEnumerable<IOrder> PreviousOrders { get; } DateTime DateJoined { get; } DateTime? LastOrder { get; } string Name { get; } IDictionary<DateTime, string> Reminders { get; } }
Eles definiram uma segunda interface que representa um pedido:
public interface IOrder { DateTime Purchased { get; } decimal Cost { get; } }
A partir dessas interfaces, a equipe poderia criar uma biblioteca para seus usuários para criar uma melhor experiência para seus clientes. Seu objetivo era criar um relacionamento mais profundo com os clientes existentes e melhorar seus relacionamentos com novos clientes.
Agora, é hora de atualizar a biblioteca para o próximo lançamento. Um dos recursos solicitados permite um desconto de fidelidade para clientes com muitos pedidos. Esse novo desconto de fidelidade é aplicado sempre que um cliente faz um pedido. O desconto específico é uma propriedade de cada cliente individual. Cada implementação do ICustomer pode definir regras diferentes para o desconto de fidelidade.
A maneira mais natural de adicionar essa funcionalidade é aprimorar a interface do ICustomer
com um método para aplicar qualquer desconto de fidelidade. Essa sugestão de design causou preocupação entre desenvolvedores experientes: "As interfaces são imutáveis depois que são lançadas! Esta é uma mudança radical! ” O C # 8.0 adiciona implementações de interface padrão para atualizar interfaces. Os autores da biblioteca podem adicionar novos membros à interface e fornecer uma implementação padrão para esses membros.
As implementações de interface padrão permitem que os desenvolvedores atualizem uma interface enquanto ainda permitem que qualquer implementador substitua essa implementação. Os usuários da biblioteca podem aceitar a implementação padrão como uma alteração sem interrupção. Se suas regras de negócios forem diferentes, elas poderão substituir.
Atualizar com membros da interface padrão
A equipe concordou com a implementação padrão mais provável: um desconto de fidelidade para os clientes.
A atualização deve fornecer a funcionalidade para definir duas propriedades: o número de pedidos necessários para se qualificar para o desconto e a porcentagem do desconto. Isso o torna um cenário perfeito para os membros da interface padrão. Você pode adicionar um método à interface do ICustomer e fornecer a implementação mais provável.Todas as implementações existentes e quaisquer novas implementações podem usar a implementação padrão ou fornecer suas próprias.
Primeiro, adicione o novo método à implementação:
O autor da biblioteca escreveu um primeiro teste para verificar a implementação:
SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31)) { Reminders = { { new DateTime(2010, 08, 12), "childs's birthday" }, { new DateTime(1012, 11, 15), "anniversary" } } }; SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m); c.AddOrder(o); o = new SampleOrder(new DateTime(2103, 7, 4), 25m); c.AddOrder(o);
Observe a seguinte parte do teste:
Essa SampleCustomer
de SampleCustomer
para ICustomer
é necessária. A classe SampleCustomer
não precisa fornecer uma implementação para ComputeLoyaltyDiscount
; isso é fornecido pela interface do ICustomer
. No entanto, a classe SampleCustomer
não herda membros de suas interfaces. Essa regra não mudou. Para chamar qualquer método declarado e implementado na interface, a variável deve ser o tipo da interface, ICustomer
neste exemplo.
Fornecer parametrização
Esse é um bom começo. Mas, a implementação padrão é muito restritiva. Muitos consumidores deste sistema podem escolher limites diferentes para o número de compras, uma duração diferente de associação ou um desconto percentual diferente.Você pode oferecer uma melhor experiência de atualização para mais clientes, fornecendo uma maneira de definir esses parâmetros. Vamos adicionar um método estático que define esses três parâmetros que controlam a implementação padrão:
Há muitos novos recursos de idioma mostrados nesse pequeno fragmento de código.As interfaces agora podem incluir membros estáticos, incluindo campos e métodos.Modelos de acesso diferentes também estão habilitados. Os campos adicionais são privados, o novo método é público. Qualquer um dos modificadores é permitido nos membros da interface.
Os aplicativos que usam a fórmula geral para calcular o desconto de lealdade, mas com parâmetros diferentes, não precisam fornecer uma implementação personalizada; eles podem definir os argumentos através de um método estático. Por exemplo, o código a seguir define uma "apreciação do cliente" que recompensa qualquer cliente com mais de um mês de associação:
ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m); Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Estender a implementação padrão
O código que você adicionou até agora forneceu uma implementação conveniente para os cenários em que os usuários desejam algo como a implementação padrão ou para fornecer um conjunto de regras não relacionadas. Para um recurso final, refatoremos um pouco o código para permitir cenários em que os usuários podem querer desenvolver a implementação padrão.
Considere uma startup que deseja atrair novos clientes. Eles oferecem um desconto de 50% no primeiro pedido de um novo cliente. Caso contrário, os clientes existentes receberão o desconto padrão. O autor da biblioteca precisa mover a implementação padrão para um método protected static
, para que qualquer classe que implemente essa interface possa reutilizar o código em sua implementação. A implementação padrão do membro da interface também chama esse método compartilhado:
public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this); protected static decimal DefaultLoyaltyDiscount(ICustomer c) { DateTime start = DateTime.Now - length; if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount)) { return discountPercent; } return 0; }
Em uma implementação de uma classe que implementa essa interface, a substituição pode chamar o método auxiliar estático e estender essa lógica para fornecer o desconto de "novo cliente":
public decimal ComputeLoyaltyDiscount() { if (PreviousOrders.Any() == false) return 0.50m; else return ICustomer.DefaultLoyaltyDiscount(this); }
Você pode ver todo o código finalizado em nosso [repositório de amostras no GitHub] (Você pode obter o aplicativo inicial no nosso repositório de amostras no GitHub .
Esses novos recursos significam que as interfaces podem ser atualizadas com segurança quando houver uma implementação padrão razoável para esses novos membros. Projete cuidadosamente interfaces para expressar idéias funcionais únicas que podem ser implementadas por várias classes. Isso facilita a atualização dessas definições de interface quando novos requisitos são descobertos para a mesma ideia funcional.