Partilhar via


19 Interfaces

19.1 Generalidades

Uma interface define um contrato. Uma classe ou estrutura que implemente uma interface deve aderir ao seu contrato. Uma interface pode herdar de várias interfaces base, e uma classe ou struct pode implementar várias interfaces.

As interfaces podem conter vários tipos de membros, conforme descrito no §19.4. A interface em si pode fornecer uma implementação para alguns ou todos os membros da função que declara. Os membros para os quais a interface não fornece uma implementação são abstratos. Suas implementações devem ser fornecidas por classes ou estruturas que implementam a interface, ou interface derivada que fornecem uma definição predominante.

Nota: Historicamente, a adição de um novo membro de função a uma interface afetava todos os consumidores existentes desse tipo de interface; foi uma mudança de rutura. A adição de implementações de membros da função de interface permitiu que os desenvolvedores atualizassem uma interface enquanto ainda permitiam que quaisquer implementadores substituíssem essa implementação. Os usuários da interface podem aceitar a implementação como uma mudança ininterrupta; no entanto, se seus requisitos forem diferentes, eles podem substituir as implementações fornecidas. Nota final

19.2 Declarações de interface

19.2.1 Generalidades

Um interface_declaration é um type_declaration (§14.7) que declara um novo tipo de interface.

interface_declaration
    : attributes? interface_modifier* 'partial'? 'interface'
      identifier variant_type_parameter_list? interface_base?
      type_parameter_constraints_clause* interface_body ';'?
    ;

Um interface_declaration consiste num conjunto opcional de atributos (§23), seguido por um conjunto opcional de interface_modifiers (§19.2.2), seguido por um modificador parcial opcional (§15.2.7), seguido pela palavra-chave interface e um identificador que nomeia a interface, seguido por uma especificação variant_type_parameter_list opcional (§19.2.3), seguida por uma especificação interface_base opcional (§19.2.4), seguido de uma especificação opcional do type_parameter_constraints_clause(§15.2.5), seguida de um interface_body (§19.3), opcionalmente seguido de ponto e vírgula.

Uma declaração de interface não deve fornecer type_parameter_constraints_clauses a menos que também forneça um variant_type_parameter_list.

Uma declaração de interface que fornece um variant_type_parameter_list é uma declaração de interface genérica. Além disso, qualquer interface aninhada dentro de uma declaração de classe genérica ou uma declaração struct genérica é ela própria uma declaração de interface genérica, uma vez que os argumentos de tipo para o tipo que contém devem ser fornecidos para criar um tipo construído (§8.4).

19.2.2 Modificadores de interface

Um interface_declaration pode, opcionalmente, incluir uma sequência de modificadores de interface:

interface_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§24.2) só está disponível em código não seguro (§24).

É um erro em tempo de compilação para o mesmo modificador aparecer várias vezes em uma declaração de interface.

O new modificador só é permitido em interfaces definidas dentro de uma classe. Especifica que a interface oculta um membro herdado com o mesmo nome, conforme descrito no §15.3.5.

Os publicmodificadores , protected, internale private controlam a acessibilidade da interface. Dependendo do contexto em que a declaração de interface ocorre, apenas alguns desses modificadores podem ser permitidos (§7.5.2). Quando uma declaração de tipo parcial (§15.2.7) inclui uma especificação de acessibilidade (através dos modificadores public, protected, internal, e private), aplicam-se as regras em §15.2.2.

19.2.3 Listas de parâmetros de tipo variante

19.2.3.1 Generalidades

As listas de parâmetros de tipo variante só podem ocorrer em tipos de interfaces e de delegados. A diferença em relação aos type_parameter_list comuns é a variance_annotation opcional em cada parâmetro de tipo.

variant_type_parameter_list
    : '<' variant_type_parameter (',' variant_type_parameter)* '>'
    ;

variant_type_parameter
    : attributes? variance_annotation? type_parameter
    ;

variance_annotation
    : 'in'
    | 'out'
    ;

Se a anotação de variância for out, o parâmetro type é dito ser covariante. Se a anotação de variância for in, o parâmetro de tipo é considerado contravariante. Se não houver anotação de variância, o parâmetro type é dito invariante.

Exemplo: No seguinte:

interface C<out X, in Y, Z>
{
    X M(Y y);
    Z P { get; set; }
}

X é covariante, Y é contravariante e é invariante Z .

Exemplo final

Se uma interface genérica for declarada em várias partes (§15.2.3), cada declaração parcial deve especificar a mesma variância para cada parâmetro de tipo.

19.2.3.2 Segurança da variância

A ocorrência de anotações de variância na lista de parâmetros de tipo de um tipo restringe os locais onde os tipos podem ocorrer dentro da declaração de tipo.

Um tipo T é inseguro para saída se uma das seguintes condições for verdadeira:

  • T é um parâmetro de tipo contravariante
  • T é um tipo de matriz com um tipo de elemento não seguro de saída
  • T é um tipo de interface ou delegado Sᵢ,... Aₑ construído a partir de um tipo S<Xᵢ, ... Xₑ> genérico onde, para pelo menos um Aᵢ, verifica-se uma das seguintes condições:
    • Xᵢ é covariante ou invariante e Aᵢ não é seguro em termos de saída.
    • Xᵢ é contravariante ou invariante e Aᵢ não é seguro para entrada.

Um tipo T é inseguro de entrada se uma das seguintes condições for verdadeira:

  • T é um parâmetro de tipo covariante
  • T é um tipo de matriz com um tipo de elemento não seguro de entrada
  • T é um tipo de interface ou delegado S<Aᵢ,... Aₑ> construído a partir de um tipo S<Xᵢ, ... Xₑ> genérico onde, para pelo menos um Aᵢ, verifica-se uma das seguintes condições:
    • Xᵢ é covariante ou invariante e Aᵢ não é seguro para entrada.
    • Xᵢ é contravariante ou invariante e Aᵢ é insegura em termos de saída.

Intuitivamente, um tipo de saída não segura é proibido em uma posição de saída, e um tipo de entrada não segura é proibido em uma posição de entrada.

Um tipo é seguro para saída se não for inseguro para saída e seguro para entrada se não for inseguro.

19.2.3.3 Conversão de variância

O objetivo das anotações de variância é fornecer conversões mais brandas (mas ainda seguras para tipos) para tipos de interface e delegados. Para o efeito, as definições de conversões implícitas (§10.2) e explícitas (§10.3) utilizam a noção de variância-convertibilidade, que é definida da seguinte forma:

Um tipo T<Aᵢ, ..., Aᵥ> é conversível em variância para um tipo T<Bᵢ, ..., Bᵥ> se T for uma interface ou um tipo delegado declarado com os parâmetros T<Xᵢ, ..., Xᵥ>de tipo variante e, para cada parâmetro Xᵢ de tipo de variante, uma das seguintes retenções:

  • Xᵢ é covariante e existe uma referência implícita ou conversão de identidade de Aᵢ para Bᵢ
  • Xᵢ é contravariante e existe uma referência implícita ou conversão de identidade de Bᵢ para Aᵢ
  • Xᵢ é invariante e existe uma conversão de identidade de Aᵢ para Bᵢ

19.2.4 Interfaces de base

Uma interface pode herdar de zero ou mais tipos de interface, que são chamados de interfaces base explícitasda interface. Quando uma interface tem uma ou mais interfaces base explícitas, na declaração dessa interface, o identificador de interface é seguido por dois pontos e uma lista separada por vírgulas de tipos de interface base.

Uma interface derivada pode declarar novos membros que ocultam membros herdados (§7.7.2.3) declarados em interfaces base ou implementar explicitamente membros herdados (§19.6.2) declarados em interfaces base.

interface_base
    : ':' interface_type_list
    ;

As interfaces de base explícitas podem ser construídas tipos de interface (§8.4, §19.2). Uma interface base não pode ser um parâmetro de tipo por si só, embora possa envolver os parâmetros de tipo que estão no escopo.

Para um tipo de interface construída, as interfaces de base explícitas são formadas ao considerar as declarações explícitas de interfaces base na declaração de tipo genérica e ao substituir, para cada type_parameter na declaração de interface de base, o correspondente type_argument do tipo construído.

As interfaces de base explícitas de uma interface devem ser pelo menos tão acessíveis como a própria interface (ponto 7.5.5).

Nota: Por exemplo, é um erro em tempo de compilação especificar uma private ou internal interface no interface_base de uma public interface. Nota final

É um erro em tempo de compilação que uma interface herde direta ou indiretamente de si mesma.

As interfaces basede uma interface são as interfaces base explícitas e suas interfaces base. Em outras palavras, o conjunto de interfaces base é o fechamento transitivo completo das interfaces base explícitas, das suas interfaces base e assim por diante. Uma interface herda todos os membros das suas interfaces base.

Exemplo: No código seguinte

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

as interfaces de base de IComboBox são IControl, ITextBox, e IListBox. Em outras palavras, a IComboBox interface acima herda membros SetText e SetItems bem como Paint.

Exemplo final

Os membros herdados de um tipo genérico construído são herdados após a substituição do tipo. Ou seja, todos os tipos constituintes no membro têm os parâmetros de tipo da declaração de classe base substituídos pelos argumentos de tipo correspondentes usados na especificação class_base .

Exemplo: No código seguinte

interface IBase<T>
{
    T[] Combine(T a, T b);
}

interface IDerived : IBase<string[,]>
{
    // Inherited: string[][,] Combine(string[,] a, string[,] b);
}

A interface IDerived herda o método Combine depois que o parâmetro do tipo T é substituído por string[,].

Exemplo final

Uma classe ou struct que implementa uma interface também implementa implicitamente todas as interfaces base da interface.

O tratamento de interfaces em várias partes de uma declaração de interface parcial (§15.2.7) é discutido mais pormenorizadamente no §15.2.4.3.

Todas as interfaces de base de uma interface devem ser seguras para a saída (§19.2.3.2).

19.3 Corpo da interface

O interface_body de uma interface define os membros da interface.

interface_body
    : '{' interface_member_declaration* '}'
    ;

19.4 Membros da interface

19.4.1 Generalidades

Os membros de uma interface são os membros herdados das interfaces base e os membros declarados pela própria interface.

interface_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | static_constructor_declaration
    | operator_declaration
    | type_declaration
    ;

Esta cláusula aumenta a descrição dos membros nas classes (§15.3) com restrições para interfaces. Os membros da interface são declarados usando member_declarations com as seguintes regras adicionais:

  • Não é permitida a finalizer_declaration .
  • Construtores de instância, constructor_declarations, não são permitidos.
  • Todos os membros da interface têm implicitamente acesso público; no entanto, um modificador de acesso explícito (§7.5.2) é permitido, exceto em construtores estáticos (§15.12).
  • O abstract modificador está implícito para membros da função de interface sem corpos, esse modificador pode ser dado explicitamente.
  • Um membro da função de instância de interface cuja declaração inclui um corpo é um membro implícito, virtual a menos que o sealed modificador ou private seja usado. O virtual modificador pode ser dado explicitamente.
  • private Um sealed membro ou função de uma interface deve ter um corpo.
  • Um private membro da função não deve ter o modificador sealed.
  • Uma interface derivada pode substituir um membro abstrato ou virtual declarado em uma interface base.
  • Um membro da função explicitamente implementado não deve ter o modificador sealed.

Algumas declarações, como constant_declaration (§15.4) não têm restrições nas interfaces.

Os membros herdados de uma interface especificamente não fazem parte do espaço de declaração da interface. Assim, uma interface pode declarar um membro com o mesmo nome ou assinatura que um membro herdado. Quando isso ocorre, diz-se que o membro da interface derivada oculta o membro da interface base. Ocultar um membro herdado não é considerado um erro, mas resulta em um aviso (§7.7.2.3).

Se um new modificador for incluído numa declaração que não oculte um membro herdado, um aviso será emitido em relação a isso.

Nota: Os membros em classe object não são, a rigor, membros de qualquer interface (§19.4). No entanto, os membros em classe object estão disponíveis através da pesquisa de membros em qualquer tipo de interface (§12.5). Nota final

O conjunto de membros de uma interface declarado em várias partes (§15.2.7) é a união dos membros declarados em cada parte. Os corpos de todas as partes da declaração de interface partilham o mesmo espaço de declaração (§7.3), e o âmbito de cada membro (§7.7) estende-se aos corpos de todas as partes.

Exemplo: Considere uma interface IA com uma implementação para um membro M e uma propriedade P. Um tipo C de implementação não fornece uma implementação para qualquer um ou MP. Eles devem ser acessados através de uma referência cujo tipo de tempo de compilação é uma interface que é implicitamente conversível em IA ou IB. Esses membros não são encontrados por meio da pesquisa de membros em uma variável do tipo C.

interface IA
{
    public int P { get { return 10; } }
    public void M()
    {
        Console.WriteLine("IA.M");
    }
}

interface IB : IA
{
    public new int P { get { return 20; } }
    void IA.M()
    {
        Console.WriteLine("IB.M");
    }
}

class C : IB { }

class Test
{
    public static void Main()
    {
        C c = new C();
        ((IA)c).M();                               // cast needed
        Console.WriteLine($"IA.P = {((IA)c).P}");  // cast needed
        Console.WriteLine($"IB.P = {((IB)c).P}");  // cast needed
    }
}

Dentro das interfaces IA e IB, membro M é acessível diretamente pelo nome. No entanto, dentro do método Main, não podemos escrever c.M() ou c.P, como esses nomes não são visíveis. Para encontrá-los, são necessários moldes para o tipo de interface apropriado. A declaração de in M usa sintaxe explícita de implementação de IB interface. Isso é necessário para fazer com que esse método substitua o de um em IA; o modificador override não pode ser aplicado a um membro da função. Exemplo final

19.4.2 Campos de interface

Esta cláusula aumenta a descrição dos campos nas classes §15.5 para campos declarados em interfaces.

Os campos de interface são declarados usando field_declarations (§15.5.1) com as seguintes regras adicionais:

  • É um erro em tempo de compilação para field_declaration declarar um campo de instância.

Exemplo: O seguinte programa contém membros estáticos de vários tipos:

public interface IX
{
    public const int Constant = 100;
    protected static int field;

    static IX()
    {
        Console.WriteLine("static members initialized");
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
        field = 50;
        Console.WriteLine("static constructor has run");
    }
}

public class Test: IX
{
    public static void Main()
    {
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
    }
}

A produção produzida é

static members initialized
constant = 100, field = 0
static constructor has run
constant = 100, field = 50

Exemplo final

Consulte §19.4.8 para obter informações sobre a alocação e inicialização de campos estáticos.

19.4.3 Métodos de interface

Esta cláusula aumenta a descrição de métodos nas classes §15.6 para métodos declarados em interfaces.

Os métodos de interface são declarados usando method_declarations (§15.6)). Os atributos, return_type, ref_return_type, identificador e parameter_list de uma declaração de método de interface têm o mesmo significado que os de uma declaração de método em uma classe. Os métodos de interface têm as seguintes regras adicionais:

  • method_modifier não inclui override.

  • Um método cujo corpo é um ponto-e-vírgula (;) é abstract; o abstract modificador não é necessário, mas é permitido.

  • Uma declaração de método de interface que tem um corpo de bloco ou corpo de expressão como um method_body é virtual; o virtual modificador não é necessário, mas é permitido.

  • Um method_declaration só pode ter type_parameter_constraints_clausese tiver um type_parameter_list.

  • A lista de requisitos para combinações válidas de modificadores declaradas para um método de classe é alargada do seguinte modo:

    • Uma declaração estática que não seja externa deve ter um corpo de bloco ou um corpo de expressão como method_body.
    • Uma declaração virtual que não seja externa deve ter um corpo de bloco ou um corpo de expressão como method_body.
    • Uma declaração particular que não seja externa deve ter um corpo de bloco ou um corpo de expressão como method_body.
    • Uma declaração selada que não seja externa deve ter um corpo de bloco ou um corpo de expressão como method_body.
    • Uma declaração assíncrona deve ter um corpo de bloco ou um corpo de expressão como method_body.
  • Todos os tipos de parâmetros de um método de interface devem ser seguros para a entrada (§19.2.3.2) e o tipo de retorno deve ser seguro para a void saída ou para a saída.

  • Quaisquer tipos de parâmetros de saída ou de referência devem também ser seguros para a produção.

    Nota: Os parâmetros de saída devem ser seguros para entrada devido a restrições comuns de implementação. Nota final

  • Cada restrição de tipo de classe, restrição de tipo de interface e restrição de parâmetro de tipo em qualquer parâmetro de tipo do método deve ser segura para entradas.

Essas regras garantem que qualquer uso covariante ou contravariante da interface permaneça seguro para digitação.

Exemplo:

interface I<out T>
{
    void M<U>() where U : T;     // Error
}

está mal formado porque a utilização de T como restrição de parâmetro de tipo em U não é segura para entradas.

Se esta restrição não estivesse em vigor, seria possível violar a segurança do tipo da seguinte maneira:

interface I<out T>
{
    void M<U>() where U : T;
}
class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<D>() {...} 
}

...

I<B> b = new C();
b.M<E>();

Trata-se, na verdade, de um apelo à C.M<E>. Mas essa chamada exige que E derive de D, então a segurança do tipo seria violada aqui.

Exemplo final

Nota: Veja §19.4.2 para um exemplo que não apenas mostra um método estático com uma implementação, mas como esse método é chamado Main e tem o tipo de retorno e assinatura corretos, também é um ponto de entrada. Nota final

Um método virtual com implementação declarada em uma interface pode ser substituído para ser abstrato em uma interface derivada. Isso é conhecido como reabstração.

Exemplo:

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

interface IB: IA
{
    abstract void IA.M();    // reabstraction of M
}

Isso é útil em interfaces derivadas onde a implementação de um método é inadequada e uma implementação mais apropriada deve ser fornecida pela implementação de classes. Exemplo final

19.4.4 Propriedades da interface

Esta cláusula aumenta a descrição de propriedades em classes §15.7 para propriedades declaradas em interfaces.

As propriedades da interface são declaradas usando property_declarations (§15.7.1) com as seguintes regras adicionais:

  • property_modifier não inclui override.

  • Uma implementação explícita de membro da interface não deve conter um accessor_modifier (§15.7.3).

  • Uma interface derivada pode implementar explicitamente uma propriedade de interface abstrata declarada em uma interface base.

    Nota: Como uma interface não pode conter campos de instância, uma propriedade de interface não pode ser uma propriedade automática de instância, pois isso exigiria a declaração de campos de instância ocultos implícitos. Nota final

  • O tipo de propriedade de interface deve ser seguro para a saída se houver um acessor get e deve ser seguro para entrada se houver um acessor definido.

  • Uma declaração de método de interface que tem um corpo de bloco ou corpo de expressão como um method_body é virtual; o virtual modificador não é necessário, mas é permitido.

  • Uma instância property_declaration que não tem implementação é abstract; o abstract modificador não é necessário, mas é permitido. Nunca é considerada uma propriedade implementada automaticamente (§15.7.4).

19.4.5 Eventos da interface

Esta cláusula aumenta a descrição de eventos em classes §15.8 para eventos declarados em interfaces.

Os eventos de interface são declarados usando event_declarations (§15.8.1), com as seguintes regras adicionais:

  • event_modifier não inclui override.
  • Uma interface derivada pode implementar um evento de interface abstrato declarado em uma interface base (§15.8.5).
  • É um erro em tempo de compilação para variable_declarators em uma instância event_declaration para conter qualquer variable_initializers.
  • Um evento de instância com os virtual modificadores ou sealed deve declarar acessadores. Nunca é considerado um evento semelhante a um campo implementado automaticamente (§15.8.2).
  • Um evento de instância com o abstract modificador não deve declarar acessadores.
  • O tipo de evento de interface deve ser seguro para entradas.

19.4.6 Indexadores de interface

Esta cláusula aumenta a descrição dos indexadores nas classes §15.9 para indexadores declarados em interfaces.

Os indexadores de interface são declarados usando indexer_declarations (§15.9), com as seguintes regras adicionais:

  • indexer_modifier não inclui override.

  • Um indexer_declaration que tem um corpo de expressão ou contém um acessor com um corpo de bloco ou corpo de expressão é virtual; o virtual modificador não é necessário, mas é permitido.

  • Um indexer_declaration cujos corpos acessadores são ponto-e-vírgula (;) é abstract; o abstract modificador não é necessário, mas é permitido.

  • Todos os tipos de parâmetros de um indexador de interface devem ser seguros para entradas (§19.2.3.2).

  • Quaisquer tipos de parâmetros de saída ou de referência devem também ser seguros para a produção.

    Nota: Os parâmetros de saída devem ser seguros para entrada devido a restrições comuns de implementação. Nota final

  • O tipo de indexador de interface deve ser seguro para saída se houver um acessor get e deve ser seguro para entrada se houver um acessor de set.

19.4.7 Operadores de interface

Esta cláusula aumenta a descrição dos membros operator_declaration nas classes §15.10 para operadores declarados em interfaces.

Um operator_declaration numa interface é a implementação (§19.1).

É um erro em tempo de compilação para uma interface declarar um operador de conversão, igualdade ou desigualdade.

19.4.8 Construtores estáticos de interface

Esta cláusula aumenta a descrição de construtores estáticos em classes §15.12 para construtores estáticos declarados em interfaces.

O construtor estático para uma interface fechada (§8.4.3) é executado no máximo uma vez em um determinado domínio de aplicativo. A execução de um construtor estático é acionada pela primeira das seguintes ações a ocorrer dentro de um domínio de aplicativo:

  • Qualquer um dos membros estáticos da interface são referenciados.
  • Antes de o Main método ser chamado para uma interface contendo o método (Main) em que a execução começa.
  • Essa interface fornece uma implementação para um membro, e essa implementação é acessada como a implementação mais específica (§19.4.10) para esse membro.

Nota: No caso em que nenhuma das ações anteriores ocorre, o construtor estático para uma interface pode não ser executado para um programa onde instâncias de tipos que implementam a interface são criadas e usadas. Nota final

Para inicializar um novo tipo de interface fechada, primeiro é criado um novo conjunto de campos estáticos para esse tipo fechado específico. Cada um dos campos estáticos é inicializado com seu valor padrão. Em seguida, os inicializadores de campo estático são executados para esses campos estáticos. Finalmente, o construtor estático é executado.

Nota: Ver §19.4.2 para um exemplo da utilização de vários tipos de membros estáticos (incluindo um método Main) declarados numa interface. Nota final

19.4.9 Tipos aninhados de interface

Esta cláusula aumenta a descrição de tipos aninhados em classes §15.3.9 para tipos aninhados declarados em interfaces.

É um erro declarar um tipo de classe, tipo struct ou tipo enum dentro do escopo de um parâmetro de tipo que foi declarado com um variance_annotation (§19.2.3.1).

Exemplo: A declaração abaixo C é um erro.

interface IOuter<out T>
{
    class C { } // error: class declaration within scope of variant type parameter 'T'
}

Exemplo final

19.4.10 Aplicação mais específica

Cada classe e struct deve ter uma implementação mais específica para cada membro virtual declarado em todas as interfaces implementadas por esse tipo entre as implementações que aparecem no tipo ou suas interfaces diretas e indiretas. A implementação mais específica é uma implementação única que é mais específica do que qualquer outra implementação.

Nota: A regra de implementação mais específica garante que uma ambiguidade decorrente da herança da interface diamante seja resolvida explicitamente pelo programador no ponto em que o conflito ocorre. Nota final

Para um tipo T que é um struct ou uma classe que implementa interfaces I2 e I3, onde I2 e I3 ambos derivam direta ou indiretamente da interface I que declara um membro M, a implementação mais específica de M é:

  • Se T declarar uma implementação de I.M, essa implementação é a implementação mais específica.
  • Caso contrário, se T for uma classe e uma classe base direta ou indireta declarar uma implementação de I.M, a classe base mais derivada de T é a implementação mais específica.
  • Caso contrário, se I2 e I3 são interfaces implementadas por T e I3 deriva direta I2 ou indiretamente, I3.M é uma implementação mais específica do que I2.M.
  • Caso contrário, nem I2.MI3.M são mais específicos e ocorre um erro.

Exemplo:

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

interface IB : IA
{
    void IA.M() { Console.WriteLine("IB.M"); }
}

interface IC: IA
{
    void IA.M() { Console.WriteLine("IC.M"); }
}

abstract class C: IB, IC { } // error: no most specific implementation for 'IA.M'

abstract class D: IA, IB, IC // OK
{
    public abstract void M();
}

A regra de implementação mais específica garante que um conflito (ou seja, uma ambiguidade decorrente da herança de diamantes) seja resolvido explicitamente pelo programador no ponto em que o conflito surge. Exemplo final

19.4.11 Acesso de membros da interface

Os membros da interface são acessados por meio de expressões de acesso de membro (§12.8.7) e acesso de indexador (§12.8.12.4) do formulário I.M e I[A], onde I é um tipo de interface, M é uma constante, campo, método, propriedade ou evento desse tipo de interface e A é uma lista de argumentos de indexador.

Em uma classe D, com classe Bbase direta ou indireta , onde B direta ou indiretamente implementa interface I e I define um método M(), a expressão base.M() é válida apenas se base.M() estáticamente (§12.3) se liga a uma implementação de em um tipo de M() classe.

Para interfaces que são estritamente de herança única (cada interface na cadeia de herança tem exatamente zero ou uma interface base direta), os efeitos das regras de pesquisa de membro (§12.5), invocação de método (§12.8.10.2) e acesso ao indexador (§12.8.12.4) são exatamente os mesmos que para classes e estruturas: Mais membros derivados ocultam menos membros derivados com o mesmo nome ou assinatura. No entanto, para interfaces de herança múltipla, ambiguidades podem ocorrer quando duas ou mais interfaces base não relacionadas declaram membros com o mesmo nome ou assinatura. Esta subcláusula apresenta vários exemplos, alguns dos quais conduzem a ambiguidades e outros não. Em todos os casos, moldes explícitos podem ser usados para resolver as ambiguidades.

Exemplo: No código seguinte

interface IList
{
    int Count { get; set; }
}

interface ICounter
{
    int Count { get; set; }
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
        x.Count = 1;             // Error
        ((IList)x).Count = 1;    // Ok, invokes IList.Count.set
        ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count
    }
}

A primeira instrução causa um erro em tempo de compilação devido à ambiguidade na pesquisa de membros (§12.5) de Count em IListCounter. Como ilustrado pelo exemplo, a ambiguidade é resolvida convertendo x para o tipo de interface base apropriado. Essas conversões não têm custos de tempo de execução — consistem apenas em tratar a instância como um tipo menos derivado em tempo de compilação.

Exemplo final

Exemplo: No código seguinte

interface IInteger
{
    void Add(int i);
}

interface IDouble
{
    void Add(double d);
}

interface INumber : IInteger, IDouble {}

class C
{
    void Test(INumber n)
    {
        n.Add(1);             // Invokes IInteger.Add
        n.Add(1.0);           // Only IDouble.Add is applicable
        ((IInteger)n).Add(1); // Only IInteger.Add is a candidate
        ((IDouble)n).Add(1);  // Only IDouble.Add is a candidate
    }
}

A invocação n.Add(1) seleciona IInteger.Add aplicando as regras de resolução de sobrecarga do §12.6.4. Da mesma forma, a invocação n.Add(1.0) seleciona IDouble.Add. Quando moldes explícitos são inseridos, há apenas um método candidato e, portanto, nenhuma ambiguidade.

Exemplo final

Exemplo: No código seguinte

interface IBase
{
    void F(int i);
}

interface ILeft : IBase
{
    new void F(int i);
}

interface IRight : IBase
{
    void G();
}

interface IDerived : ILeft, IRight {}

class A
{
    void Test(IDerived d)
    {
        d.F(1);           // Invokes ILeft.F
        ((IBase)d).F(1);  // Invokes IBase.F
        ((ILeft)d).F(1);  // Invokes ILeft.F
        ((IRight)d).F(1); // Invokes IBase.F
    }
}

o membro IBase.F é escondido pelo ILeft.F. A invocação d.F(1) seleciona assim ILeft.F, mesmo que IBase.F pareça não estar oculto no caminho de acesso que passa por IRight.

A regra intuitiva para se esconder em interfaces de herança múltipla é simplesmente esta: se um membro estiver oculto em qualquer caminho de acesso, ele estará oculto em todos os caminhos de acesso. Porque o caminho de acesso de IDerived a ILeft a IBase esconde IBase.F, o membro também está oculto no caminho de acesso de IDerived a IRight a IBase.

Exemplo final

19.5 Nomes de membros qualificados da interface

Um membro da interface às vezes é referido por seu nome de membro qualificado da interface. O nome qualificado de um membro da interface consiste no nome da interface na qual o membro é declarado, seguido por um ponto, seguido pelo nome do membro. O nome qualificado de um membro faz referência à interface na qual o membro é declarado.

Exemplo: Dadas as declarações

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

o nome qualificado de Paint é IControl.Paint e o nome qualificado de SetText é ITextBox.SetText. No exemplo acima, não é possível referir-se a Paint como ITextBox.Paint.

Exemplo final

Quando uma interface faz parte de um namespace, um nome de membro qualificado da interface pode incluir o nome do namespace.

Exemplo:

namespace GraphicsLib
{
    interface IPolygon
    {
        void CalculateArea();
    }
}

Dentro do GraphicsLib namespace, ambos IPolygon.CalculateArea e GraphicsLib.IPolygon.CalculateArea são nomes de membros qualificados da interface para o CalculateArea método.

Exemplo final

19.6 Implementações de interface

19.6.1 Generalidades

As interfaces podem ser implementadas por classes e structs. Para indicar que uma classe ou struct implementa diretamente uma interface, a interface é incluída na lista de classes base da classe ou struct.

Uma classe ou struct C que implementa uma interface I deve fornecer ou herdar uma implementação para cada membro declarado que IC pode acessar. Os membros públicos de I podem ser definidos em membros públicos de C. Os membros não públicos declarados em I que são acessíveis podem ser definidos usando CC a implementação de interface explícita (§19.6.2).

Um membro em um tipo derivado que satisfaz o mapeamento de interface (§19.6.5), mas não implementa o membro da interface base correspondente, introduz um novo membro. Isso ocorre quando a implementação explícita da interface é necessária para definir o membro da interface.

Exemplo:

interface ICloneable
{
    object Clone();
}

interface IComparable
{
    int CompareTo(object other);
}

class ListEntry : ICloneable, IComparable
{
    public object Clone() {...}    
    public int CompareTo(object other) {...}
}

Exemplo final

Uma classe ou struct que implementa diretamente uma interface também implementa implicitamente todas as interfaces base da interface. Isso é verdadeiro mesmo se a classe ou struct não listar explicitamente todas as interfaces base na lista de classes base.

Exemplo:

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    public void Paint() {...}
    public void SetText(string text) {...}
}

Aqui, a classe TextBox implementa tanto IControl como ITextBox.

Exemplo final

Quando uma classe C implementa diretamente uma interface, todas as classes derivadas de C também implementam a interface implicitamente.

As interfaces de base especificadas numa declaração de classe podem ser construídas em tipos de interface (§8.4, §19.2).

Exemplo: O código a seguir ilustra como uma classe pode implementar tipos de interface construídos:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

Exemplo final

As interfaces de base de uma declaração de classe genérica devem satisfazer a regra de unicidade descrita no ponto 19.6.3.

19.6.2 Implementações explícitas de membros da interface

Para fins de implementação de interfaces, uma classe, struct ou interface pode declarar implementações explícitas de membros da interface. Uma implementação explícita de membro da interface é um método, propriedade, evento ou declaração de indexador que faz referência a um nome de membro qualificado da interface. Uma classe ou struct que implementa um membro não público em uma interface base deve declarar uma implementação explícita de membro da interface. Uma interface que implementa um membro em uma interface base deve declarar uma implementação explícita de membro da interface.

Um membro de interface derivado que satisfaz o mapeamento de interface (§19.6.5) oculta o membro da interface base (§7.7.2). O compilador deve emitir um aviso, a menos que o new modificador esteja presente.

Exemplo:

interface IList<T>
{
    T[] GetElements();
}

interface IDictionary<K, V>
{
    V this[K key] { get; }
    void Add(K key, V value);
}

class List<T> : IList<T>, IDictionary<int, T>
{
    public T[] GetElements() {...}
    T IDictionary<int, T>.this[int index] {...}
    void IDictionary<int, T>.Add(int index, T value) {...}
}

Aqui IDictionary<int,T>.this e IDictionary<int,T>.Add são implementações explícitas de membros da interface.

Exemplo final

Exemplo: Em alguns casos, o nome de um membro da interface pode não ser apropriado para a classe de implementação, caso em que o membro da interface pode ser implementado usando a implementação explícita do membro da interface. Uma classe que implementa uma abstração de arquivo, por exemplo, provavelmente implementaria uma função membro que tem o efeito de liberar o recurso de arquivo e, também, implementaria o método da interface Close usando a implementação explícita de membros da interface Dispose.

interface IDisposable
{
    void Dispose();
}

class MyFile : IDisposable
{
    void IDisposable.Dispose() => Close();

    public void Close()
    {
        // Do what's necessary to close the file
        System.GC.SuppressFinalize(this);
    }
}

Exemplo final

Não é possível acessar uma implementação explícita de membro da interface por meio de seu nome de membro qualificado da interface em uma chamada de método, acesso à propriedade, acesso a eventos ou acesso ao indexador. Uma implementação explícita de membro da instância de interface só pode ser acessada por meio de uma instância de interface e, nesse caso, é referenciada simplesmente por seu nome de membro. Uma implementação explícita de membro estático da interface só pode ser acessada através do nome da interface.

É um erro em tempo de compilação para uma implementação explícita de membro da interface incluir quaisquer modificadores (§15.6) diferentes de extern ou async.

Uma implementação de método de interface explícita herda quaisquer restrições de parâmetro de tipo da interface.

Uma type_parameter_constraints_clause sobre uma implementação de método de interface explícita só pode consistir em class ou structprimary_constraints aplicados a type_parameters que são conhecidos, de acordo com as restrições herdadas, como sendo tipos de referência ou de valor, respectivamente. Qualquer tipo de formulário T? na assinatura da implementação do método de interface explícito, onde T é um parâmetro type, é interpretado da seguinte forma:

  • Se uma class restrição for adicionada para o parâmetro de tipo T, então T? é um tipo de referência anulável; caso contrário
  • Se não houver nenhuma restrição adicionada, ou uma restrição struct for adicionada, para o parâmetro de tipo T, então T? é um tipo de valor anulável.

Exemplo: O seguinte demonstra como as regras funcionam quando os parâmetros de tipo estão envolvidos:

#nullable enable
interface I
{
    void Foo<T>(T? value) where T : class;
    void Foo<T>(T? value) where T : struct;
}

class C : I
{
    void I.Foo<T>(T? value) where T : class { }
    void I.Foo<T>(T? value) where T : struct { }
}

Sem a restrição do parâmetro where T : class, o método base com um parâmetro de tipo de referência não pode ser substituído. Exemplo final

Nota: As implementações explícitas de membros da interface têm características de acessibilidade diferentes dos outros membros. Como as implementações explícitas de membro da interface nunca são acessíveis por meio de um nome de membro de interface qualificado em uma invocação de método ou um acesso de propriedade, elas são, de certa forma, privadas. No entanto, uma vez que podem ser acedidos através da interface, são, de certa forma, também tão públicos como a interface em que são declarados. As implementações explícitas de membros da interface servem a dois propósitos principais:

  • Como as implementações explícitas de membro da interface não são acessíveis por meio de instâncias de classe ou struct, elas permitem que as implementações de interface sejam excluídas da interface pública de uma classe ou struct. Isso é particularmente útil quando uma classe ou struct implementa uma interface interna que não é de interesse para um consumidor dessa classe ou struct.
  • Implementações explícitas de membros da interface permitem a desambiguação de membros da interface com a mesma assinatura. Sem implementações explícitas de membros da interface, seria impossível para uma classe, struct ou interface ter implementações diferentes de membros da interface com a mesma assinatura e tipo de retorno, como seria impossível para uma classe, struct ou interface ter qualquer implementação de membros da interface com a mesma assinatura, mas com diferentes tipos de retorno.

Nota final

Para que uma implementação explícita de membro da interface seja válida, a classe, struct ou interface deve nomear uma interface em sua classe base ou lista de interface base que contenha um membro cujo nome de membro de interface qualificado, tipo, número de parâmetros de tipo e tipos de parâmetros correspondam exatamente aos da implementação explícita de membro da interface. Se um membro de função de interface tiver um array de parâmetros, o parâmetro correspondente de uma implementação associada de membro de interface explícita pode, mas não é necessário, ter o modificador params. Se o membro da função de interface não tiver uma matriz de parâmetros, uma implementação de membro de interface explícita associada não deve ter uma matriz de parâmetros.

Exemplo: Assim, na seguinte classe

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
    int IComparable.CompareTo(object other) {...} // invalid
}

A declaração de IComparable.CompareTo resulta em um erro em tempo de compilação porque IComparable não está listada na lista de classes base de Shape e não é uma interface base de ICloneable. Do mesmo modo, nas declarações

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
}

class Ellipse : Shape
{
    object ICloneable.Clone() {...} // invalid
}

A declaração de ICloneable.Clone em Ellipse resulta em um erro durante a compilação porque ICloneable não está explicitamente listada na lista de classes base de Ellipse.

Exemplo final

O nome de membro qualificado de uma implementação explícita de membro da interface deve fazer referência à interface na qual o membro foi declarado.

Exemplo: Assim, nas declarações

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
}

a implementação explícita do membro da interface do Paint deve ser escrita como IControl.Paint, não ITextBox.Paint.

Exemplo final

19.6.3 Singularidade das interfaces implementadas

As interfaces implementadas por uma declaração genérica de tipo devem permanecer únicas para todos os tipos construídos possíveis. Sem esta regra, seria impossível determinar o método correto para chamar certos tipos construídos.

Exemplo: Suponha que uma declaração de classe genérica tenha permissão para ser escrita da seguinte maneira:

interface I<T>
{
    void F();
}

class X<U ,V> : I<U>, I<V> // Error: I<U> and I<V> conflict
{
    void I<U>.F() {...}
    void I<V>.F() {...}
}

Se isso fosse permitido, seria impossível determinar qual código executar no seguinte caso:

I<int> x = new X<int, int>();
x.F();

Exemplo final

Para determinar se a lista de interfaces de uma declaração de tipo genérica é válida, as seguintes etapas são executadas:

  • Seja L a lista de interfaces especificadas diretamente em uma classe genérica, struct ou declaração Cde interface.
  • Adicione a L qualquer interface base das interfaces já em L.
  • Remova todas as duplicatas do L.
  • Se qualquer possível tipo construído criado a partir de C , após os argumentos de tipo serem substituídos em L, fazer com que duas interfaces em L sejam idênticas, então a declaração de C é inválida. As declarações de restrição não são consideradas ao determinar todos os tipos construídos possíveis.

Nota: Na declaração de classe acima X, a lista de interfaces L consiste em l<U> e I<V>. A declaração é inválida porque qualquer tipo construído com U e V sendo o mesmo tipo faria com que essas duas interfaces fossem tipos idênticos. Nota final

É possível que interfaces especificadas em diferentes níveis de herança unifiquem:

interface I<T>
{
    void F();
}

class Base<U> : I<U>
{
    void I<U>.F() {...}
}

class Derived<U, V> : Base<U>, I<V> // Ok
{
    void I<V>.F() {...}
}

Este código é válido ainda que Derived<U,V> implemente tanto I<U> quanto I<V>. O código

I<int> x = new Derived<int, int>();
x.F();

invoca o método em Derived, uma vez que Derived<int,int>' efetivamente reimplementa I<int> (§19.6.7).

19.6.4 Aplicação de métodos genéricos

Quando um método genérico implementa implicitamente um método de interface, as restrições dadas para cada parâmetro de tipo de método devem ser equivalentes em ambas as declarações (depois de quaisquer parâmetros de tipo de interface serem substituídos pelos argumentos de tipo adequados), em que os parâmetros de tipo de método são identificados por posições ordinais, da esquerda para a direita.

Exemplo: No código seguinte:

interface I<X, Y, Z>
{
    void F<T>(T t) where T : X;
    void G<T>(T t) where T : Y;
    void H<T>(T t) where T : Z;
}

class C : I<object, C, string>
{
    public void F<T>(T t) {...}                  // Ok
    public void G<T>(T t) where T : C {...}      // Ok
    public void H<T>(T t) where T : string {...} // Error
}

o método C.F<T> implementa I<object,C,string>.F<T>implicitamente . Neste caso, não é necessário (nem permitido) especificar a restriçãoC.F<T>, T: object uma vez que object é uma restrição implícita em todos os parâmetros de tipo. O método C.G<T> implementa implicitamente o I<object,C,string>.G<T> porque as restrições correspondem às da interface, depois de os parâmetros de tipo da interface serem substituídos pelos argumentos de tipo correspondentes. A restrição para o método C.H<T> é um erro porque os tipos selados (string neste caso) não podem ser usados como restrições. Omitir a restrição também seria um erro, uma vez que as restrições de implementações de método de interface implícitas são necessárias para corresponder. Assim, é impossível implementar I<object,C,string>.H<T>implicitamente . Este método de interface só pode ser implementado usando uma implementação explícita de membro da interface:

class C : I<object, C, string>
{
    ...
    public void H<U>(U u) where U : class {...}

    void I<object, C, string>.H<T>(T t)
    {
        string s = t; // Ok
        H<T>(t);
    }
}

Nesse caso, a implementação explícita do membro da interface invoca um método público com restrições estritamente mais fracas. A atribuição de t a s é válida, uma vez que T herda uma restrição de T: string, mesmo que essa restrição não seja expressável no código-fonte. Exemplo final

Nota: Quando um método genérico implementa explicitamente um método de interface, não são permitidas restrições no método de implementação (§15.7.1, §19.6.2). Nota final

19.6.5 Mapeamento de interface

Uma classe ou struct deve fornecer implementações de todos os membros abstratos das interfaces listados na lista de classes base da classe ou struct. O processo de localização de implementações de membros da interface em uma classe ou struct de implementação é conhecido como mapeamento de interface.

O mapeamento de interface para uma classe ou struct C localiza uma implementação para cada membro de cada interface especificada na lista de classes base de C. A implementação de um membro I.Mde interface particular, onde I é a interface na qual o membro M é declarado, é determinada examinando cada classe, interface ou struct S, começando com C e repetindo para cada classe base sucessiva e interface implementada de C, até que uma correspondência seja localizada:

  • Se S contiver uma declaração de uma implementação explícita de membro da interface que corresponda a I e M, então esse membro é a implementação de I.M.
  • Caso contrário, se S contiver uma declaração de um membro público não estático que corresponda ao M, então esse membro será a implementação do I.M. Se mais de um membro coincidir, não está especificado qual membro é a implementação do I.M. Esta situação só pode ocorrer se S for um tipo construído onde os dois membros declarados no tipo genérico têm assinaturas diferentes, mas os argumentos de tipo tornam suas assinaturas idênticas.

Um erro em tempo de compilação ocorre se as implementações não puderem ser localizadas para todos os membros de todas as interfaces especificadas na lista de classes base de C. Os membros de uma interface incluem os membros que são herdados das interfaces base.

Considera-se que os membros de um tipo de interface construído têm quaisquer parâmetros de tipo substituídos pelos argumentos de tipo correspondentes, conforme especificado no §15.3.3.

Exemplo: Por exemplo, dada a declaração de interface genérica:

interface I<T>
{
    T F(int x, T[,] y);
    T this[int y] { get; }
}

A interface I<string[]> construída tem os membros:

string[] F(int x, string[,][] y);
string[] this[int y] { get; }

Exemplo final

Para fins de mapeamento de interface, um membro A de classe, interface ou struct corresponde a um membro B da interface quando:

  • A e B são métodos, e as listas de nomes, tipos e parâmetros de A e B são idênticas.
  • Ae B são propriedades, o nome e o tipo de e A são idênticos, e B tem os mesmos acessadores que A (B tem permissão para ter acessadores adicionais se não for uma implementação explícita de membro da A interface).
  • A e B são eventos, e o nome e o tipo de A e B são idênticos.
  • Ae B são indexadores, o tipo e as listas de parâmetros de e A são idênticos, e B tem os mesmos acessadores que A (B tem permissão para ter acessadores adicionais se não for uma implementação explícita de membro da A interface).

Implicações notáveis do algoritmo de mapeamento de interface são:

  • As implementações explícitas de membros da interface têm precedência sobre outros membros na mesma classe ou estrutura ao determinar o membro da classe ou estrutura que implementa um membro da interface.
  • Nem membros não-públicos nem estáticos participam do mapeamento de interface.

Exemplo: No código seguinte

interface ICloneable
{
    object Clone();
}

class C : ICloneable
{
    object ICloneable.Clone() {...}
    public object Clone() {...}
}

O membro ICloneable.Clone de C torna-se a implementação de Clone em ICloneable porque implementações explícitas de membros da interface têm precedência sobre outros membros.

Exemplo final

Se uma classe ou struct implementa duas ou mais interfaces contendo um membro com o mesmo nome, tipo e tipos de parâmetro, é possível mapear cada um desses membros de interface para um único membro de classe ou struct.

Exemplo:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

class Page : IControl, IForm
{
    public void Paint() {...}
}

Aqui, os Paint métodos de ambos IControl e IForm são mapeados para o Paint método em Page. É claro que também é possível ter implementações de membros de interface explícitos separados para os dois métodos.

Exemplo final

Se uma classe ou struct implementa uma interface que contém membros ocultos, então alguns membros podem precisar ser implementados por meio de implementações explícitas de membros da interface.

Exemplo:

interface IBase
{
    int P { get; }
}

interface IDerived : IBase
{
    new int P();
}

Uma implementação desta interface exigiria pelo menos uma implementação explícita de membro da interface e assumiria uma das seguintes formas

class C1 : IDerived
{
    int IBase.P { get; }
    int IDerived.P() {...}
}
class C2 : IDerived
{
    public int P { get; }
    int IDerived.P() {...}
}
class C3 : IDerived
{
    int IBase.P { get; }
    public int P() {...}
}

Exemplo final

Quando uma classe implementa várias interfaces que têm a mesma interface base, pode haver apenas uma implementação da interface base.

Exemplo: No código seguinte

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

class ComboBox : IControl, ITextBox, IListBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
    void IListBox.SetItems(string[] items) {...}
}

Não é possível ter implementações separadas para o IControl nomeado na lista de classes base, o IControl herdado por ITextBoxe o IControl herdado por IListBox. Na verdade, não há noção de uma identidade separada para essas interfaces. Em vez disso, as implementações de ITextBoxe IListBox compartilham a mesma implementação de IControl, e ComboBox é simplesmente considerado para implementar três interfaces, IControl, ITextBox, e IListBox.

Exemplo final

Os membros de uma classe base participam do mapeamento de interface.

Exemplo: No código seguinte

interface Interface1
{
    void F();
}

class Class1
{
    public void F() {}
    public void G() {}
}

class Class2 : Class1, Interface1
{
    public new void G() {}
}

O método F em Class1 é usado na Class2's implementação de Interface1.

Exemplo final

19.6.6 Herança da implementação da interface

Uma classe herda todas as implementações de interface fornecidas por suas classes base.

Sem reimplementar explicitamente uma interface, uma classe derivada não pode, de forma alguma, alterar os mapeamentos de interface que herda de suas classes base.

Exemplo: Nas declarações

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public void Paint() {...}
}

class TextBox : Control
{
    public new void Paint() {...}
}

O método Paint em TextBox oculta o método Paint em Control, mas não altera o mapeamento de Control.Paint para IControl.Paint. As chamadas para instâncias de classe e instâncias de interface Paint terão os seguintes efeitos.

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();

Exemplo final

No entanto, quando um método de interface é mapeado em um método virtual em uma classe, é possível que as classes derivadas substituam o método virtual e alterem a implementação da interface.

Exemplo: Reescrever as declarações acima para

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public virtual void Paint() {...}
}

class TextBox : Control
{
    public override void Paint() {...}
}

Os seguintes efeitos serão agora observados

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes TextBox.Paint();

Exemplo final

Como as implementações explícitas de membros da interface não podem ser declaradas virtuais, não é possível substituir uma implementação explícita de membro da interface. No entanto, é perfeitamente válido para uma implementação de membro de interface explícita chamar outro método, e esse outro método pode ser declarado virtual para permitir que as classes derivadas o substituam.

Exemplo:

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() { PaintControl(); }
    protected virtual void PaintControl() {...}
}

class TextBox : Control
{
    protected override void PaintControl() {...}
}

Aqui, as classes derivadas de Control podem especializar a implementação de IControl.Paint substituindo o PaintControl método.

Exemplo final

19.6.7 Reimplementação da interface

Uma classe que herda uma implementação de interface tem permissão para reimplementar a interface incluindo-a na lista de classes base.

Uma reimplementação de uma interface segue exatamente as mesmas regras de mapeamento de interface que uma implementação inicial de uma interface. Assim, o mapeamento de interface herdado não tem qualquer efeito sobre o mapeamento de interface estabelecido para a re-implementação da interface.

Exemplo: Nas declarações

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() {...}
}

class MyControl : Control, IControl
{
    public void Paint() {}
}

O facto de Control mapear IControl.Paint em Control.IControl.Paint não afeta a reimplementação no MyControl, que mapeia IControl.Paint para MyControl.Paint.

Exemplo final

As declarações de membros públicos herdados e as declarações de membros de interface explícitos herdados participam no processo de mapeamento de interface para interfaces reimplementadas.

Exemplo:

interface IMethods
{
    void F();
    void G();
    void H();
    void I();
}

class Base : IMethods
{
    void IMethods.F() {}
    void IMethods.G() {}
    public void H() {}
    public void I() {}
}

class Derived : Base, IMethods
{
    public void F() {}
    void IMethods.H() {}
}

Aqui, a implementação de IMethods em Derived mapeia os métodos de interface em Derived.F, Base.IMethods.G, Derived.IMethods.H e Base.I.

Exemplo final

Quando uma classe implementa uma interface, ela implicitamente também implementa todas as interfaces base dessa interface. Da mesma forma, uma re-implementação de uma interface também é implicitamente uma re-implementação de todas as interfaces base da interface.

Exemplo:

interface IBase
{
    void F();
}

interface IDerived : IBase
{
    void G();
}

class C : IDerived
{
    void IBase.F() {...}
    void IDerived.G() {...}
}

class D : C, IDerived
{
    public void F() {...}
    public void G() {...}
}

Aqui, a re-implementação de IDerived também re-implementa IBase, mapeando IBase.F em D.F.

Exemplo final

19.6.8 Classes abstratas e interfaces

Como uma classe não abstrata, uma classe abstrata deve fornecer implementações de todos os membros abstratos das interfaces listadas na lista de classes base da classe. No entanto, uma classe abstrata tem permissão para mapear métodos de interface em métodos abstratos.

Exemplo:

interface IMethods
{
    void F();
    void G();
}

abstract class C : IMethods
{
    public abstract void F();
    public abstract void G();
    }

Aqui, a implementação de IMethods mapeia F e G para métodos abstratos, que devem ser redefinidos em classes não abstratas que derivam de C.

Exemplo final

Implementações explícitas de membros da interface não podem ser abstratas, mas implementações explícitas de membros da interface são naturalmente permitidas para chamar métodos abstratos.

Exemplo:

interface IMethods
{
    void F();
    void G();
}

abstract class C: IMethods
{
    void IMethods.F() { FF(); }
    void IMethods.G() { GG(); }
    protected abstract void FF();
    protected abstract void GG();
}

Aqui, classes não abstratas que derivam de C seriam obrigadas a substituir FF e GG, fornecendo assim a implementação real de IMethods.

Exemplo final