18 Interfaces
18.1 Geral
Uma interface define um contrato. Uma classe ou struct que implementa 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 métodos, propriedades, eventos e indexadores. A interface em si não fornece implementações para os membros que ela declara. A interface apenas especifica os membros que devem ser fornecidos por classes ou structs que implementam a interface.
18.2 Declarações de interface
18.2.1 Geral
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 em um conjunto opcional de atributos (§22), seguido por um conjunto opcional de interface_modifiers (§18.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 (§18.2.3), seguido por um interface_base opcional Especificação (§18.2.4), seguida por uma especificação type_parameter_constraints_clauses opcional (§15.2.5), seguida por um interface_body (§18.3), opcionalmente seguido por um ponto-e-vírgula.
Uma declaração de interface não deve fornecer um type_parameter_constraints_clauses a menos que forneça também 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 de struct genérica é em si 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).
18.2.2 Modificadores de interface
Um interface_declaration pode incluir opcionalmente uma sequência de modificadores de interface:
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) está disponível apenas em código não seguro (§23).
É um erro de 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. Ele especifica que a interface oculta um membro herdado com o mesmo nome, conforme descrito em §15.3.5.
Os public
modificadores , protected
, internal
e 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 (por meio dos public
modificadores , protected
, internal
, e private
), as regras em §15.2.2 se aplicam.
18.2.3 Listas de parâmetros de tipo de variante
18.2.3.1 Geral
As listas de parâmetros de tipo de variante só podem ocorrer em tipos de interface e delegados. A diferença em relação aos type_parameter_lists comuns é a variance_annotation opcional em cada parâmetro de tipo.
variant_type_parameter_list
: '<' variant_type_parameters '>'
;
variant_type_parameters
: attributes? variance_annotation? type_parameter
| variant_type_parameters ',' attributes? variance_annotation?
type_parameter
;
variance_annotation
: 'in'
| 'out'
;
Se a anotação de variância for out
, o parâmetro de tipo é considerado 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 de tipo é considerado invariável.
Exemplo: No seguinte:
interface C<out X, in Y, Z> { X M(Y y); Z P { get; set; } }
X
é covariante,Y
é contravariante e é invarianteZ
.exemplo de fim
Se uma interface genérica for declarada em várias partes (§15.2.3), cada declaração parcial deverá especificar a mesma variação para cada parâmetro de tipo.
18.2.3.2 Segurança de variação
A ocorrência de anotações de variação 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 de saída se um dos seguintes for válido:
T
é um parâmetro do tipo contravarianteT
é um tipo de matriz com um tipo de elemento inseguro de saídaT
é um tipoSᵢ,... Aₑ
de interface ou delegado construído a partir de um tipoS<Xᵢ, ... Xₑ>
genérico em que, pelo menos, umAᵢ
dos seguintes recursos é válido:Xᵢ
é covariante ou invariante eAᵢ
não é seguro para a saída.Xᵢ
é contravariante ou invariante eAᵢ
não é seguro para a entrada.
Um tipo T não é seguro para entrada se um dos seguintes for válido:
T
é um parâmetro de tipo covarianteT
é um tipo de matriz com um tipo de elemento inseguro de entradaT
é um tipoS<Aᵢ,... Aₑ>
de interface ou delegado construído a partir de um tipoS<Xᵢ, ... Xₑ>
genérico em que, pelo menos, umAᵢ
dos seguintes recursos é válido:Xᵢ
é covariante ou invariante eAᵢ
não é seguro para a entrada.Xᵢ
é contravariante ou invariante eAᵢ
não é seguro para a saída.
Intuitivamente, um tipo inseguro de saída é proibido em uma posição de saída e um tipo inseguro de entrada é 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 para entrada.
18.2.3.3 Conversão de variação
A finalidade das anotações de variação é fornecer conversões mais brandas (mas ainda seguras para o tipo) para tipos de interface e delegado. Para esse fim, as definições de conversões implícitas (§10.2) e explícitas (§10.3) fazem uso da noção de variância-conversibilidade, que é definida da seguinte forma:
Um tipo T<Aᵢ, ..., Aᵥ>
é conversível por variância em 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 variante, um dos seguintes é válido:
Xᵢ
é covariante e existe uma referência implícita ou conversão de identidade deAᵢ
paraBᵢ
Xᵢ
é contravariante e existe uma referência implícita ou conversão de identidade deBᵢ
paraAᵢ
Xᵢ
é invariante e existe uma conversão de identidade deAᵢ
paraBᵢ
18.2.4 Interfaces básicas
Uma interface pode herdar de zero ou mais tipos de interface, que são chamados de interfaces base explícitas da 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.
interface_base
: ':' interface_type_list
;
As interfaces base explícitas podem ser tipos de interface construídos (§8.4, §18.2). Uma interface base não pode ser um parâmetro de tipo por conta própria, embora possa envolver os parâmetros de tipo que estão no escopo.
Para um tipo de interface construída, as interfaces base explícitas são formadas usando as declarações explícitas de interface base na declaração de tipo genérico e substituindo, por cada type_parameter na declaração de interface base, o type_argument correspondente do tipo construído.
As interfaces de base explícitas de uma interface devem ser pelo menos tão acessíveis quanto a própria interface (§7.5.5).
Observação: por exemplo, é um erro em tempo de compilação especificar uma
private
interface ouinternal
no interface_base de umapublic
interface. nota final
É um erro de tempo de compilação para uma interface herdar direta ou indiretamente de si mesma.
As interfaces base de 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, suas interfaces base explícitas e assim por diante. Uma interface herda todos os membros de suas interfaces base.
Exemplo: no código a seguir
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}
As interfaces básicas de
IComboBox
sãoIControl
,ITextBox
eIListBox
. Em outras palavras, aIComboBox
interface acima herda membrosSetText
eSetItems
tambémPaint
.exemplo de fim
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 a seguir
interface IBase<T> { T[] Combine(T a, T b); } interface IDerived : IBase<string[,]> { // Inherited: string[][,] Combine(string[,] a, string[,] b); }
A interface
IDerived
herda oCombine
método depois que o parâmetroT
type é substituído porstring[,]
.exemplo de fim
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 adiante em §15.2.4.3.
Cada interface base de uma interface deve ser segura para saída (§18.2.3.2).
18.3 Corpo da interface
O interface_body de uma interface define os membros da interface.
interface_body
: '{' interface_member_declaration* '}'
;
18.4 Membros da interface
18.4.1 Geral
Os membros de uma interface são os membros herdados das interfaces base e os membros declarados pela própria interface.
interface_member_declaration
: interface_method_declaration
| interface_property_declaration
| interface_event_declaration
| interface_indexer_declaration
;
Uma declaração de interface declara zero ou mais membros. Os membros de uma interface devem ser métodos, propriedades, eventos ou indexadores. Uma interface não pode conter constantes, campos, operadores, construtores de instância, finalizadores ou tipos, nem pode conter membros estáticos de qualquer tipo.
Todos os membros da interface têm acesso público implicitamente. É um erro em tempo de compilação que as declarações de membro da interface incluam modificadores.
Um interface_declaration cria um novo espaço de declaração (§7.3) e os parâmetros de tipo e interface_member_declarations imediatamente contidos pelo interface_declaration introduzir novos membros nesse espaço de declaração. As seguintes regras se aplicam a interface_member_declarations:
- O nome de um parâmetro de tipo no variant_type_parameter_list de uma declaração de interface deve ser diferente dos nomes de todos os outros parâmetros de tipo no mesmo variant_type_parameter_list e deve ser diferente dos nomes de todos os membros da interface.
- O nome de um método deve ser diferente dos nomes de todas as propriedades e eventos declarados na mesma interface. Além disso, a assinatura (§7.6) de um método deve ser diferente das assinaturas de todos os outros métodos declarados na mesma interface, e dois métodos declarados na mesma interface não devem ter assinaturas que diferem apenas por
in
,out
, eref
. - O nome de uma propriedade ou evento deve ser diferente dos nomes de todos os outros membros declarados na mesma interface.
- A assinatura de um indexador deve ser diferente das assinaturas de todos os outros indexadores declarados na mesma interface.
Os membros herdados de uma interface não fazem parte especificamente do espaço de declaração da interface. Assim, uma interface tem permissão para declarar um membro com o mesmo nome ou assinatura de 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 faz com que o compilador emita um aviso. Para suprimir o aviso, a declaração do membro da interface derivada deve incluir um new
modificador para indicar que o membro derivado se destina a ocultar o membro base. Este tópico é discutido mais detalhadamente em §7.7.2.3.
Se um new
modificador for incluído em uma declaração que não oculte um membro herdado, um aviso será emitido para esse efeito. Esse aviso é suprimido removendo o new
modificador.
Nota: Os membros da classe
object
não são, estritamente falando, membros de nenhuma interface (§18.4). No entanto, os membros da classeobject
estão disponíveis por meio da pesquisa de membros em qualquer tipo de interface (§12.5). nota final
O conjunto de membros de uma interface declarada 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 compartilham o mesmo espaço de declaração (§7.3) e o escopo de cada membro (§7.7) se estende aos corpos de todas as partes.
18.4.2 Métodos de interface
Os métodos de interface são declarados usando interface_method_declarations:
interface_method_declaration
: attributes? 'new'? return_type interface_method_header
| attributes? 'new'? ref_kind ref_return_type interface_method_header
;
interface_method_header
: identifier '(' parameter_list? ')' ';'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
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 (§15.6). Uma declaração de método de interface não tem permissão para especificar um corpo de método e, portanto, a declaração sempre termina com um ponto-e-vírgula.
Todos os tipos de parâmetros de um método de interface devem ser seguros para entrada (§18.2.3.2) e o tipo de retorno deve ser void
seguro para saída ou saída. Além disso, quaisquer tipos de parâmetros de saída ou de referência também devem ser seguros para a saída.
Observação: os parâmetros de saída devem ser seguros para entrada devido a restrições comuns de implementação. nota final
Além disso, 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 entrada.
Além disso, 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 entrada.
Essas regras garantem que qualquer uso covariante ou contravariante da interface permaneça seguro para tipo.
Exemplo:
interface I<out T> { void M<U>() where U : T; // Error }
está mal formado porque o uso de
T
como uma restrição de parâmetro de tipo emU
não é seguro para entrada.Se essa restrição não estivesse em vigor, seria possível violar a segurança de tipo da seguinte maneira:
class B {} class D : B {} class E : B {} class C : I<D> { public void M<U>() {...} } ... I<B> b = new C(); b.M<E>();
Na verdade, isso é uma chamada para
C.M<E>
. Mas essa chamada requer queE
derive de , então a segurança deD
tipo seria violada aqui.exemplo de fim
18.4.3 Propriedades da interface
As propriedades da interface são declaradas usando interface_property_declarations:
interface_property_declaration
: attributes? 'new'? type identifier '{' interface_accessors '}'
| attributes? 'new'? ref_kind type identifier '{' ref_interface_accessor '}'
;
interface_accessors
: attributes? 'get' ';'
| attributes? 'set' ';'
| attributes? 'get' ';' attributes? 'set' ';'
| attributes? 'set' ';' attributes? 'get' ';'
;
ref_interface_accessor
: attributes? 'get' ';'
;
Os atributos, o tipo e o identificador de uma declaração de propriedade de interface têm o mesmo significado que os de uma declaração de propriedade em uma classe (§15.7).
Os acessadores de uma declaração de propriedade de interface correspondem aos acessadores de uma declaração de propriedade de classe (§15.7.3), exceto que o accessor_body sempre deve ser um ponto-e-vírgula. Assim, os acessadores simplesmente indicam se a propriedade é leitura-gravação, somente leitura ou somente gravação.
O tipo de uma propriedade de interface deve ser seguro para saída se houver um acessador get e deve ser seguro para entrada se houver um acessador definido.
18.4.4 Eventos de interface
Os eventos de interface são declarados usando interface_event_declarations:
interface_event_declaration
: attributes? 'new'? 'event' type identifier ';'
;
Os atributos, o tipo e o identificador de uma declaração de evento de interface têm o mesmo significado que os de uma declaração de evento em uma classe (§15.8).
O tipo de um evento de interface deve ser seguro para entrada.
18.4.5 Indexadores de interface
Os indexadores de interface são declarados usando interface_indexer_declarations:
interface_indexer_declaration
: attributes? 'new'? type 'this' '[' parameter_list ']'
'{' interface_accessors '}'
| attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
'{' ref_interface_accessor '}'
;
Os atributos, o tipo e a parameter_list de uma declaração do indexador de interface têm o mesmo significado que os de uma declaração do indexador em uma classe (§15.9).
Os acessadores de uma declaração do indexador de interface correspondem aos acessadores de uma declaração do indexador de classe (§15.9), exceto que o accessor_body deve ser sempre um ponto-e-vírgula. Assim, os acessadores simplesmente indicam se o indexador é leitura/gravação, somente leitura ou somente gravação.
Todos os tipos de parâmetros de um indexador de interface devem ser seguros para entrada (§18.2.3.2). Além disso, quaisquer tipos de parâmetros de saída ou de referência também devem ser seguros para a saída.
Observação: os parâmetros de saída devem ser seguros para entrada devido a restrições comuns de implementação. nota final
O tipo de um indexador de interface deve ser seguro para saída se houver um acessador get e deve ser seguro para entrada se houver um acessador definido.
18.4.6 Acesso ao membro da interface
Os membros da interface são acessados por meio de expressões de acesso de membro (§12.8.7) e acesso ao indexador (§12.8.11.3) do formulário I.M
e I[A]
, onde I
é um tipo de interface, M
é um método, propriedade ou evento desse tipo de interface e A
é uma lista de argumentos do indexador.
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.9.2) e acesso ao indexador (§12.8.11.3) são exatamente os mesmos que para classes e structs: Mais membros derivados ocultam menos membros derivados com o mesmo nome ou assinatura. No entanto, para interfaces de herança múltipla, podem ocorrer ambiguidades quando duas ou mais interfaces base não relacionadas declaram membros com o mesmo nome ou assinatura. Esta subcláusula mostra vários exemplos, alguns dos quais levam a ambiguidades e outros não. Em todos os casos, conversões explícitas podem ser usadas para resolver as ambiguidades.
Exemplo: no código a seguir
interface IList { int Count { get; set; } } interface ICounter { void Count(int i); } interface IListCounter : IList, ICounter {} class C { void Test(IListCounter x) { x.Count(1); // Error x.Count = 1; // Error ((IList)x).Count = 1; // Ok, invokes IList.Count.set ((ICounter)x).Count(1); // Ok, invokes ICounter.Count } }
As duas primeiras instruções causam erros em tempo de compilação porque a pesquisa de membro (§12.5) de
Count
InIListCounter
é ambígua. Conforme ilustrado pelo exemplo, a ambiguidade é resolvida convertendox
para o tipo de interface base apropriado. Essas conversões não têm custos de tempo de execução — elas consistem apenas em exibir a instância como um tipo menos derivado em tempo de compilação.exemplo de fim
Exemplo: no código a seguir
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)
éIInteger.Add
selecionada aplicando regras de resolução de sobrecarga de §12.6.4. Da mesma forma, a invocaçãon.Add(1.0)
selecionaIDouble.Add
. Quando conversões explícitas são inseridas, há apenas um método candidato e, portanto, nenhuma ambiguidade.exemplo de fim
Exemplo: no código a seguir
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
IBase.F
membro é ocultadoILeft.F
pelo membro. A invocaçãod.F(1)
seleciona ,ILeft.F
mesmoIBase.F
que pareça não estar oculto no caminho de acesso que leva atravésIRight
de .A regra intuitiva para ocultar 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. Como o caminho de acesso de
IDerived
paraILeft
paraIBase
ocultaIBase.F
, o membro também está oculto no caminho de acesso deIDerived
paraIRight
paraIBase
.exemplo de fim
18.5 Nomes de membros de interface qualificados
Às vezes, um membro da interface é referido por seu nome de membro da interface qualificado. 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 se referir aPaint
ITextBox.Paint
.exemplo de fim
Quando uma interface faz parte de um namespace, um nome de membro de interface qualificado pode incluir o nome do namespace.
Exemplo:
namespace System { public interface ICloneable { object Clone(); } }
Dentro do
System
namespace, ambos eSystem.ICloneable.Clone
ICloneable.Clone
são nomes de membros de interface qualificados para oClone
método.exemplo de fim
18.6 Implementações de interface
18.6.1 Geral
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.
Exemplo:
interface ICloneable { object Clone(); } interface IComparable { int CompareTo(object other); } class ListEntry : ICloneable, IComparable { public object Clone() {...} public int CompareTo(object other) {...} }
exemplo de fim
Uma classe ou struct que implementa diretamente uma interface também implementa implicitamente todas as interfaces base da interface. Isso é verdadeiro mesmo que a classe ou struct não liste 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 ambosIControl
eITextBox
.exemplo de fim
Quando uma classe C
implementa diretamente uma interface, todas as classes derivadas também C
implementam a interface implicitamente.
As interfaces base especificadas em uma declaração de classe podem ser tipos de interface construídos (§8.4, §18.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 de fim
As interfaces base de uma declaração de classe genérica devem satisfazer a regra de exclusividade descrita em §18.6.3.
18.6.2 Implementações explícitas de membros da interface
Para fins de implementação de interfaces, uma classe ou struct pode declarar implementações explícitas de membros de interface. Uma implementação explícita de membro de interface é um método, propriedade, evento ou declaração de indexador que faz referência a um nome de membro de interface qualificado.
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
estão implementações explícitas deIDictionary<int,T>.Add
membros da interface.exemplo de fim
Exemplo: em alguns casos, o nome de um membro da interface pode não ser apropriado para a classe de implementação, nesse caso, 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
Close
função de membro que tem o efeito de liberar o recurso de arquivo e implementaria oDispose
método da interface usando aIDisposable
implementação explícita do membro da interface: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 de fim
Não é possível acessar uma implementação explícita de membro de interface por meio de seu nome de membro de interface qualificado em uma invocação de método, acesso de propriedade, acesso a eventos ou acesso ao indexador. Uma implementação explícita de membro de interface só pode ser acessada por meio de uma instância de interface e, nesse caso, é referenciada simplesmente por seu nome de membro.
É 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
.
É um erro em tempo de compilação para uma implementação explícita do método de interface incluir type_parameter_constraints_clauses. As restrições para uma implementação de método de interface explícito genérico são herdadas do método de interface.
Observação: as implementações explícitas de membros da interface têm características de acessibilidade diferentes das de outros membros. Como as implementações explícitas de membros 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 acesso a uma propriedade, elas são, em certo sentido, privadas. No entanto, uma vez que podem ser acessados por meio da interface, eles são, em certo sentido, também tão públicos quanto a interface na qual são declarados. As implementações explícitas de membros da interface servem a duas finalidades principais:
- Como as implementações explícitas de membro de 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 interessa a um consumidor dessa classe ou struct.
- As 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 de interface, seria impossível para uma classe ou struct ter implementações diferentes de membros de interface com a mesma assinatura e tipo de retorno, assim como seria impossível para uma classe ou struct ter qualquer implementação de membros de interface com a mesma assinatura, mas com diferentes tipos de retorno.
nota final
Para que uma implementação explícita de membro de interface seja válida, a classe ou struct deve nomear uma interface em sua lista de classes base que contenha um membro cujo nome de membro de interface qualificado, tipo, número de parâmetros de tipo e tipos de parâmetro correspondam exatamente aos da implementação explícita do membro da interface. Se um membro da função de interface tiver uma matriz de parâmetros, o parâmetro correspondente de uma implementação de membro de interface explícita associada será permitido, mas não obrigatório, para ter o params
modificador. Se o membro da função de interface não tiver uma matriz de parâmetros, uma implementação explícita de membro de interface associada não deverá ter uma matriz de parâmetros.
Exemplo: Assim, na classe a seguir
class Shape : ICloneable { object ICloneable.Clone() {...} int IComparable.CompareTo(object other) {...} // invalid }
A declaração de resulta em um erro em
IComparable.CompareTo
tempo de compilação porqueIComparable
não está listado na lista de classes base deShape
e não é uma interface base deICloneable
. Da mesma forma, nas declaraçõesclass Shape : ICloneable { object ICloneable.Clone() {...} } class Ellipse : Shape { object ICloneable.Clone() {...} // invalid }
A declaração de In
Ellipse
resulta em um erro deICloneable.Clone
tempo de compilação porqueICloneable
não está explicitamente listada na lista de classes base deEllipse
.exemplo de fim
O nome do membro de interface qualificado de uma implementação explícita de membro de 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ãoITextBox.Paint
.exemplo de fim
18.6.3 Unicidade 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 essa 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 possa 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 de fim
Para determinar se a lista de interfaces de uma declaração de tipo genérico é válida, as seguintes etapas são executadas:
- Seja
L
a lista de interfaces especificadas diretamente em uma declaração genérica de classe, struct ou interfaceC
. - Adicione a
L
qualquer interface base das interfaces já emL
. - Remova todas as duplicatas do
L
. - Se qualquer tipo construído possível criado a partir de, depois que os argumentos de
C
tipo forem substituídos emL
, fizer com que duas interfaces sejamL
idênticas, a declaração deC
será inválida. As declarações de restrição não são consideradas ao determinar todos os tipos construídos possíveis.
Observação: na declaração
X
de classe acima, a listaL
del<U>
interfaces consiste em eI<V>
. A declaração é inválida porque qualquer tipo construído comU
eV
sendo o mesmo tipo faria com que essas duas interfaces fossem tipos idênticos. nota final
É possível que as interfaces especificadas em diferentes níveis de herança se unificem:
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() {...}
}
Esse código é válido mesmo que Derived<U,V>
implemente ambos e I<V>
I<U>
. 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>
(§18.6.7).
18.6.4 Implementação de métodos genéricos
Quando um método genérico implementa implicitamente um método de interface, as restrições fornecidas para cada parâmetro de tipo de método devem ser equivalentes em ambas as declarações (depois que quaisquer parâmetros de tipo de interface são substituídos pelos argumentos de tipo apropriados), onde os parâmetros de tipo de método são identificados por posições ordinais, da esquerda para a direita.
Exemplo: No código a seguir:
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>
implementaI<object,C,string>.F<T>
implicitamente . Nesse caso, não é necessário (nem permitido) especificar a restriçãoT: object
,C.F<T>
poisobject
é uma restrição implícita em todos os parâmetros de tipo. O métodoC.G<T>
é implementadoI<object,C,string>.G<T>
implicitamente porque as restrições correspondem às da interface, depois que os parâmetros de tipo de interface são substituídos pelos argumentos de tipo correspondentes. A restrição para methodC.H<T>
é um erro porque os tipos sealed (string
neste caso) não podem ser usados como restrições. Omitir a restrição também seria um erro, pois as restrições de implementações implícitas do método de interface são necessárias para corresponder. Assim, é impossível implementarI<object,C,string>.H<T>
implicitamente . Esse método de interface só pode ser implementado usando uma implementação explícita de membro de 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 para s é válida, pois
T
herda uma restrição deT: string
, mesmo que essa restrição não seja expressável no código-fonte. exemplo de fim
Observação: quando um método genérico implementa explicitamente um método de interface, nenhuma restrição é permitida no método de implementação (§15.7.1, §18.6.2). nota final
18.6.5 Mapeamento de interface
Uma classe ou struct deve fornecer implementações de todos os membros das interfaces listadas na lista de classes base da classe ou struct. O processo de localização de implementações de membros de 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.M
de interface específico, onde I
é a interface na qual o membro M
é declarado, é determinada examinando cada classe ou struct S
, começando e C
repetindo para cada classe base sucessiva de C
, até que uma correspondência seja localizada:
- Se
S
contiver uma declaração de uma implementação explícita de membro de interface que corresponda aI
eM
, esse membro será a implementação deI.M
. - Caso contrário, se
S
contiver uma declaração de um membro público não estático que corresponda aM
, esse membro será a implementação deI.M
. Se mais de um membro corresponder, não será especificado qual membro é a implementação doI.M
. Essa situação só pode ocorrer seS
for um tipo construído em que os dois membros, conforme declarado no tipo genérico, têm assinaturas diferentes, mas os argumentos de tipo tornam suas assinaturas idênticas.
Ocorrerá um erro em tempo de compilação 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 de interfaces base.
Os membros de um tipo de interface construída são considerados como tendo todos os parâmetros de tipo substituídos pelos argumentos de tipo correspondentes, conforme especificado em §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 de fim
Para fins de mapeamento de interface, um membro A
de classe ou struct corresponde a um membro B
de interface quando:
A
eB
são métodos, e o nome, tipo e listas de parâmetros deA
eB
são idênticos.A
andB
são propriedades, o nome e o tipo de eB
são idênticos eA
têm os mesmos acessadores queB
(A
tem permissão para ter acessadores adicionais se não for uma implementação explícita de membro daA
interface).A
eB
são eventos, e o nome e o tipo deA
eB
são idênticos.A
eB
são indexadores, as listas de tipos e parâmetros deA
eB
são idênticas eA
têm os mesmos acessadores queB
(A
tem permissão para ter acessadores adicionais se não for uma implementação explícita de membro de interface).
As implicações notáveis do algoritmo de mapeamento de interface são:
- As implementações explícitas de membro de interface têm precedência sobre outros membros na mesma classe ou struct ao determinar a classe ou membro struct que implementa um membro de interface.
- Nem membros não públicos nem estáticos participam do mapeamento de interface.
Exemplo: no código a seguir
interface ICloneable { object Clone(); } class C : ICloneable { object ICloneable.Clone() {...} public object Clone() {...} }
o
ICloneable.Clone
membro de torna-se a implementação de em 'ICloneable' porque as implementações explícitas deC
membros daClone
interface têm precedência sobre outros membros.exemplo de fim
Se uma classe ou struct implementar duas ou mais interfaces contendo um membro com o mesmo nome, tipo e tipos de parâmetro, será possível mapear cada um desses membros de interface em uma única classe ou membro de struct.
Exemplo:
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }
Aqui, os
Paint
métodos de ambosIControl
eIForm
são mapeados para oPaint
método emPage
. É claro que também é possível ter implementações de membros de interface explícitas separadas para os dois métodos.exemplo de fim
Se uma classe ou struct implementar uma interface que contenha membros ocultos, alguns membros poderão precisar ser implementados por meio de implementações explícitas de membros de interface.
Exemplo:
interface IBase { int P { get; } } interface IDerived : IBase { new int P(); }
Uma implementação dessa 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 de fim
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 a seguir
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, oIControl
inherited byITextBox
e oIControl
inherited byIListBox
. De fato, não há noção de uma identidade separada para essas interfaces. Em vez disso, as implementações eIListBox
ITextBox
compartilham a mesma implementação deIControl
, eComboBox
é simplesmente considerado para implementar três interfaces,IControl
,ITextBox
, eIListBox
.exemplo de fim
Os membros de uma classe base participam do mapeamento de interface.
Exemplo: no código a seguir
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }
O método
F
INClass1
é usado naClass2's
implementação deInterface1
.exemplo de fim
18.6.6 Herança de implementação de 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
Paint
método emTextBox
oculta oPaint
método emControl
, mas não altera o mapeamento de ONTOIControl.Paint
, e as chamadas paraPaint
instâncias de classe through e instâncias deControl.Paint
interface terão os seguintes efeitosControl 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 de fim
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 observados agora
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 de fim
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 membros da interface. No entanto, é perfeitamente válido que uma implementação explícita de membro de interface chame outro método, e esse outro método pode ser declarado virtual para permitir que 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 substituindoIControl.Paint
oPaintControl
método.exemplo de fim
18.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 nenhum efeito sobre o mapeamento de interface estabelecido para a reimplementaçã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 fato de que mapeia para não afeta a reimplementação em
MyControl
, que mapeiaIControl.Paint
paraMyControl.Paint
.Control.IControl.Paint
IControl.Paint
Control
exemplo de fim
Declarações de membro público herdadas e declarações de membro de interface explícitas herdadas participam do 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 in
Derived
mapeia os métodos deIMethods
interface emDerived.F
,Base.IMethods.G
,Derived.IMethods.H
, eBase.I
.exemplo de fim
Quando uma classe implementa uma interface, ela implicitamente também implementa todas as interfaces base dessa interface. Da mesma forma, uma reimplementação de uma interface também é implicitamente uma reimplementaçã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 reimplementação de também reimplementa
IBase
o mapeamentoD.F
IBase.F
deIDerived
.exemplo de fim
18.6.8 Classes e interfaces abstratas
Como uma classe não abstrata, uma classe abstrata deve fornecer implementações de todos os membros 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
mapasF
eG
métodos abstratos, que devem ser substituídos em classes não abstratas que derivam deC
.exemplo de fim
As implementações explícitas de membros de interface não podem ser abstratas, mas as implementações explícitas de membros de interface têm permissão 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, as classes não abstratas que derivam de seriam obrigadas
C
a substituirFF
eGG
, fornecendo assim a implementação real deIMethods
.exemplo de fim
ECMA C# draft specification