Por que os métodos de interface C # não são declarados abstratos ou virtuais?

108

Os métodos C # em interfaces são declarados sem usar a virtualpalavra - chave e substituídos na classe derivada sem usar a overridepalavra - chave.

Existe um motivo para isso? Presumo que seja apenas uma conveniência de linguagem e, obviamente, o CLR sabe como lidar com isso nos bastidores (os métodos não são virtuais por padrão), mas há outros motivos técnicos?

Aqui está o IL que uma classe derivada gera:

class Example : IDisposable {
    public void Dispose() { }
}

.method public hidebysig newslot virtual final 
        instance void  Dispose() cil managed
{
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Example::Dispose

Observe que o método é declarado virtual finalno IL.

Robert Harvey
fonte

Respostas:

145

Para a interface, a adição de abstract, ou mesmo as publicpalavras - chave seria redundante, então você as omite:

interface MyInterface {
  void Method();
}

No CIL, o método é marcado virtuale abstract.

(Observe que o Java permite que os membros da interface sejam declarados public abstract).

Para a classe de implementação, existem algumas opções:

Não substituível : Em C # a classe não declara o método como virtual. Isso significa que ele não pode ser substituído em uma classe derivada (apenas oculto). No CIL o método ainda é virtual (mas selado) porque deve suportar polimorfismo em relação ao tipo de interface.

class MyClass : MyInterface {
  public void Method() {}
}

Substituível : Tanto em C # quanto em CIL o método é virtual. Ele participa do envio polimórfico e pode ser substituído.

class MyClass : MyInterface {
  public virtual void Method() {}
}

Explícito : esta é uma maneira de uma classe implementar uma interface, mas não fornecer os métodos de interface na interface pública da própria classe. No CIL, o método será private(!), Mas ainda poderá ser chamado de fora da classe a partir de uma referência ao tipo de interface correspondente. Implementações explícitas também não podem ser substituídas. Isso é possível porque há uma diretiva CIL ( .override) que vinculará o método privado ao método de interface correspondente que está implementando.

[C #]

class MyClass : MyInterface {
  void MyInterface.Method() {}
}

[CIL]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
  .override MyInterface::Method
}

No VB.NET, você pode até mesmo criar um alias para o nome do método da interface na classe de implementação.

[VB.NET]

Public Class MyClass
  Implements MyInterface
  Public Sub AliasedMethod() Implements MyInterface.Method
  End Sub
End Class

[CIL]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
  .override MyInterface::Method
}

Agora, considere este caso estranho:

interface MyInterface {
  void Method();
}
class Base {
  public void Method();
}
class Derived : Base, MyInterface { }

Se Basee Derivedforem declarados no mesmo assembly, o compilador se tornará Base::Methodvirtual e lacrado (no CIL), embora Basenão implemente a interface.

Se Basee Derivedestiverem em assemblies diferentes, ao compilar o Derivedassembly, o compilador não mudará o outro assembly, portanto, apresentará um membro em Derivedque será uma implementação explícita para MyInterface::Methodque apenas delegue a chamada a Base::Method.

Como você pode ver, toda implementação de método de interface deve suportar comportamento polimórfico e, portanto, deve ser marcada como virtual no CIL, mesmo que o compilador precise passar por muitos obstáculos para fazê-lo.

Jordão
fonte
73

Citando Jeffrey Ritcher de CLR via CSharp 3ª edição aqui

O CLR requer que os métodos de interface sejam marcados como virtuais. Se você não marcar explicitamente o método como virtual em seu código-fonte, o compilador marcará o método como virtual e lacrado; isso evita que uma classe derivada substitua o método de interface. Se você marcar explicitamente o método como virtual, o compilador marca o método como virtual (e o deixa sem lacre); isso permite que uma classe derivada substitua o método de interface. Se um método de interface for lacrado, uma classe derivada não pode substituir o método. No entanto, uma classe derivada pode herdar novamente a mesma interface e pode fornecer sua própria implementação para os métodos da interface.

Usman Khan
fonte
25
A citação não diz porque uma implementação de método de interface deve ser marcada como virtual. É porque ele é polimórfico em relação ao tipo de interface, então ele precisa de um slot na mesa virtual para permitir o despacho do método virtual.
Jordão
1
Não tenho permissão para marcar explicitamente o método de interface como virtual e obter o erro "erro CS0106: O modificador 'virtual' não é válido para este item". Testado usando v2.0.50727 (versão mais antiga no meu PC).
ccppjava
3
@ccppjava A partir do comentário de Jorado abaixo, você marca o membro da classe que está implementando a interface virtual para permitir que as subclasses substituam a classe.
Christopher Stevenson
13

Sim, os métodos de implementação da interface são virtuais no que diz respeito ao tempo de execução. É um detalhe de implementação, faz as interfaces funcionarem. Os métodos virtuais obtêm slots na tabela v da classe, cada slot tem um ponteiro para um dos métodos virtuais. O lançamento de um objeto para um tipo de interface gera um ponteiro para a seção da tabela que implementa os métodos de interface. O código do cliente que usa a referência de interface agora vê o primeiro ponteiro de método de interface no deslocamento 0 do ponteiro de interface, etc.

O que eu subestimei em minha resposta original é o significado do atributo final . Impede que uma classe derivada substitua o método virtual. Uma classe derivada deve reimplementar a interface, os métodos de implementação sombreiam os métodos da classe base. O que é suficiente para implementar o contrato da linguagem C # que diz que o método de implementação não é virtual.

Se você declarar o método Dispose () na classe Example como virtual, verá o atributo final sendo removido. Agora permitindo que uma classe derivada o substitua.

Hans Passant
fonte
4

Na maioria dos outros ambientes de código compilado, as interfaces são implementadas como vtables - uma lista de ponteiros para os corpos dos métodos. Normalmente, uma classe que implementa várias interfaces terá em algum lugar em seus metadados gerados pelo compilador interno uma lista de vtables de interface, uma vtable por interface (de modo que a ordem do método seja preservada). É assim que as interfaces COM também são normalmente implementadas.

No .NET, porém, as interfaces não são implementadas como vtables distintas para cada classe. Os métodos de interface são indexados por meio de uma tabela de método de interface global da qual todas as interfaces fazem parte. Portanto, não é necessário declarar um método virtual para que esse método implemente um método de interface - a tabela de métodos de interface global pode apenas apontar para o endereço de código do método da classe diretamente.

A declaração de um método virtual para implementar uma interface também não é necessária em outras linguagens, mesmo em plataformas não CLR. A linguagem Delphi no Win32 é um exemplo.

dthorpe
fonte
0

Eles não são virtuais (em termos de como pensamos neles, se não em termos de implementação subjacente como (virtual selado) - bom ler as outras respostas aqui e aprender algo eu mesmo :-)

Eles não substituem nada - não há implementação na interface.

Tudo o que a interface faz é fornecer um "contrato" ao qual a classe deve aderir - um padrão, se quiser, para que os chamadores saibam como chamar o objeto, mesmo que nunca tenham visto essa classe em particular antes.

Cabe à classe implementar o método da interface como desejar, dentro dos limites do contrato - virtual ou "não virtual" (virtual selado, como se revelou).

Jason Williams
fonte
todos neste tópico sabem para que servem as interfaces. A questão é extremamente específica - o IL gerado é virtual para o método de interface e não virtual para um método sem interface.
Rex M
5
Sim, é muito fácil criticar uma resposta depois que a pergunta foi editada, não é?
Jason Williams,
0

Interfaces são um conceito mais abstrato do que classes, quando você declara uma classe que implementa uma interface, você apenas diz "a classe deve ter esses métodos específicos da interface, e não importa se estático , virtual , não virtual , sobrescrito , como desde que tenha o mesmo ID e os mesmos parâmetros de tipo ".

Outras linguagens que suportam interfaces como Object Pascal ("Delphi") e Objective-C (Mac) também não requerem que os métodos de interface sejam marcados como virtuais e não virtuais.

Mas, você pode estar certo, acho que pode ser uma boa ideia ter um atributo "virtual" / "override" específico nas interfaces, caso você queira restringir os métodos de classes que implementam uma interface em particular. Mas, isso também significa ter palavras-chave "não virtuais", "dontcareifvirtualornot", para ambas as interfaces.

Eu entendo sua pergunta, porque vejo algo semelhante em Java, quando um método de classe tem que usar o "@virtual" ou "@override" para ter certeza de que um método se destina a ser virtual.

Umlcat
fonte
@override não muda realmente o comportamento do código ou altera o código de byte resultante. O que ele faz é sinalizar ao compilador que o método assim decorado tem a intenção de ser uma substituição, o que permite que o compilador faça algumas verificações de integridade. C # funciona de maneira diferente; overrideé uma palavra-chave de primeira classe no próprio idioma.
Robert Harvey