ti-enxame.com

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

Os métodos C # nas interfaces são declarados sem o uso da palavra-chave virtual e substituídos na classe derivada sem o uso da palavra-chave override.

Existe uma razão para isso? Suponho que seja apenas uma conveniência de linguagem e, obviamente, o CLR sabe como lidar com isso oculto (métodos não são virtuais por padrão), mas existem 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 virtualfinal na IL.

101
Robert Harvey

Para a interface, a adição das palavras-chave abstract ou mesmo public seria redundante; portanto, você as omite:

interface MyInterface {
  void Method();
}

No CIL, o método está marcado como virtual e abstract.

(Observe que 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 não pode ser substituído em uma classe derivada (apenas oculta). No CIL, o método ainda é virtual (mas selado) porque deve suportar o polimorfismo em relação ao tipo de interface.

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

Substituível: Tanto em C # como no 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 será possível chamar 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 existe uma diretiva CIL (.override) que vincula 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é usar o alias do nome do método de 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 Base e Derived forem declarados no mesmo assembly, o compilador tornará Base::Method virtual e selado (no CIL), mesmo que Base não implemente a interface.

Se Base e Derived estiverem em assemblies diferentes, ao compilar o Assembly Derived, o compilador não alterará o outro Assembly, portanto, apresentará um membro em Derived que será uma implementação explícita para MyInterface::Method que delegará apenas a chamada para Base::Method.

Então você vê, 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 faça.

139
Jordão

Citando Jeffrey Ritcher do CLR via CSharp 3rd Edition aqui

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

71
Usman Khan

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

O que subestimei na 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 shadow os métodos da classe base. O que é suficiente para implementar o contrato de 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 a substitua.

11
Hans Passant

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 do seu compilador interno metadados gerados uma lista de interfaces vtables, uma vtable por interface (para 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 através de uma tabela de métodos de interface global da qual todas as interfaces fazem parte. Portanto, não é necessário declarar um método virtual para que ele implemente um método de interface - a tabela de métodos de interface global pode apenas apontar diretamente para o endereço de código do método da classe.

Declarar um método virtual para implementar uma interface também não é necessário em outros idiomas, mesmo em plataformas não CLR. A linguagem Delphi no Win32 é um exemplo.

4
dthorpe

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 sozinho :-)

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 você preferir, para que os chamadores saibam como chamar o objeto, mesmo que nunca tenham visto essa classe específica antes.

Cabe à classe, então, implementar o método de interface como será, dentro dos limites do contrato - virtual ou "não virtual" (virtual selado como se vê).

0
Jason Williams

<joke> Isso é algo que você pode pedir a Anders Hejlsberg e ao restante da equipe de design em C #. </joke>

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

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

Mas, você pode estar certo, acho uma boa idéia ter um atributo "virtual"/"substituir" específico nas interfaces, caso você queira restringir os métodos de classe que implementam uma interface específica . Mas isso também significa ter uma palavra-chave "não virtual", "não cuida de nada virtual" para ambas as interfaces.

Entendo sua pergunta, porque vejo algo semelhante em Java, quando um método de classe precisa usar o "@virtual" ou "@override" para garantir que um método seja virtual.

0
umlcat