¿Por qué los métodos de interfaz de C # no se declaran abstractos o virtuales?

108

Los métodos de C # en las interfaces se declaran sin usar la virtualpalabra clave y se anulan en la clase derivada sin usar la overridepalabra clave.

¿Hay alguna razón para esto? Supongo que es solo una conveniencia de lenguaje, y obviamente el CLR sabe cómo manejar esto bajo las sábanas (los métodos no son virtuales por defecto), pero ¿hay otras razones técnicas?

Aquí está el IL que genera una clase derivada:

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 el método está declarado virtual finalen IL.

Robert Harvey
fuente

Respuestas:

145

Para la interfaz, la adición de abstract, o incluso las publicpalabras clave sería redundante, por lo que las omite:

interface MyInterface {
  void Method();
}

En el CIL, el método está marcado virtualy abstract.

(Tenga en cuenta que Java permite declarar miembros de la interfaz public abstract).

Para la clase de implementación, hay algunas opciones:

No reemplazable : en C #, la clase no declara el método como virtual. Eso significa que no se puede anular en una clase derivada (solo oculta). En el CIL, el método sigue siendo virtual (pero sellado) porque debe admitir polimorfismo con respecto al tipo de interfaz.

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

Reemplazable : tanto en C # como en CIL, el método es virtual. Participa en el envío polimórfico y se puede anular.

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

Explícito : esta es una forma para que una clase implemente una interfaz pero no proporcione los métodos de interfaz en la interfaz pública de la clase en sí. En el CIL, el método será private(!) Pero aún se podrá llamar desde fuera de la clase desde una referencia al tipo de interfaz correspondiente. Las implementaciones explícitas tampoco son reemplazables. Esto es posible porque hay una directiva CIL ( .override) que vinculará el método privado al método de interfaz correspondiente 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
}

En VB.NET, incluso puede alias el nombre del método de interfaz en la clase de implementación.

[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
}

Ahora, considere este extraño caso:

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

Si Basey Derivedse declaran en el mismo ensamblado, el compilador lo hará Base::Methodvirtual y sellado (en el CIL), aunque Baseno implemente la interfaz.

Si Basey Derivedestán en diferentes ensamblados, al compilar el Derivedensamblado, el compilador no cambiará el otro ensamblado, por lo que introducirá un miembro Derivedque será una implementación explícita para MyInterface::Methodque solo delegará la llamada a Base::Method.

Como puede ver, cada implementación de método de interfaz debe admitir un comportamiento polimórfico y, por lo tanto, debe marcarse como virtual en el CIL, incluso si el compilador debe pasar por aros para hacerlo.

Jordão
fuente
73

Citando a Jeffrey Ritcher de CLR a través de CSharp 3rd Edition aquí

El CLR requiere que los métodos de interfaz estén marcados como virtuales. Si no marca explícitamente el método como virtual en su código fuente, el compilador marca el método como virtual y sellado; esto evita que una clase derivada anule el método de interfaz. Si marca explícitamente el método como virtual, el compilador marca el método como virtual (y lo deja sin sellar); esto permite que una clase derivada anule el método de interfaz. Si un método de interfaz está sellado, una clase derivada no puede anular el método. Sin embargo, una clase derivada puede volver a heredar la misma interfaz y puede proporcionar su propia implementación para los métodos de la interfaz.

Usman Khan
fuente
25
La cita no dice por qué se requiere que la implementación de un método de interfaz se marque como virtual. Esto se debe a que es polimórfico con respecto al tipo de interfaz, por lo que necesita una ranura en la mesa virtual para permitir el envío de métodos virtuales.
Jordão
1
No se me permite marcar explícitamente el método de interfaz como virtual y obtener el error "error CS0106: El modificador 'virtual' no es válido para este elemento". Probado con v2.0.50727 (versión más antigua en mi PC).
ccppjava
3
@ccppjava Del comentario de Jorado a continuación, marca el miembro de la clase que está implementando la interfaz virtual para permitir que las subclases anulen la clase.
Christopher Stevenson
13

Sí, los métodos de implementación de la interfaz son virtuales en lo que respecta al tiempo de ejecución. Es un detalle de implementación, hace que las interfaces funcionen. Los métodos virtuales obtienen ranuras en la tabla v de la clase, cada ranura tiene un puntero a uno de los métodos virtuales. La conversión de un objeto a un tipo de interfaz genera un puntero a la sección de la tabla que implementa los métodos de interfaz. El código de cliente que usa la referencia de interfaz ahora ve el primer puntero de método de interfaz en el desplazamiento 0 del puntero de interfaz, etcétera.

Lo que menosprecié en mi respuesta original es el significado del atributo final . Evita que una clase derivada anule el método virtual. Una clase derivada debe volver a implementar la interfaz, los métodos de implementación hacen sombra a los métodos de la clase base. Lo cual es suficiente para implementar el contrato de lenguaje C # que dice que el método de implementación no es virtual.

Si declara el método Dispose () en la clase Example como virtual, verá que se elimina el atributo final . Ahora permite que una clase derivada lo anule.

Hans Passant
fuente
4

En la mayoría de los demás entornos de código compilado, las interfaces se implementan como vtables, una lista de punteros a los cuerpos del método. Normalmente, una clase que implementa múltiples interfaces tendrá en algún lugar de sus metadatos generados por el compilador interno una lista de vtables de interfaz, una vtable por interfaz (para que se mantenga el orden del método). Así es como se implementan normalmente las interfaces COM.

En .NET, sin embargo, las interfaces no se implementan como tablas virtuales distintas para cada clase. Los métodos de interfaz se indexan a través de una tabla de métodos de interfaz global de la que forman parte todas las interfaces. Por lo tanto, no es necesario declarar un método virtual para que ese método implemente un método de interfaz; la tabla de métodos de interfaz global puede apuntar directamente a la dirección de código del método de la clase.

La declaración de un método virtual para implementar una interfaz tampoco es necesaria en otros lenguajes, incluso en plataformas que no son CLR. El lenguaje Delphi en Win32 es un ejemplo.

dthorpe
fuente
0

No son virtuales (en términos de cómo pensamos en ellos, si no en términos de implementación subyacente como (virtual sellado) - es bueno leer las otras respuestas aquí y aprender algo yo mismo :-)

No anulan nada, no hay implementación en la interfaz.

Todo lo que hace la interfaz es proporcionar un "contrato" al que la clase debe adherirse, un patrón, si lo desea, para que las personas que llaman sepan cómo llamar al objeto incluso si nunca antes han visto esa clase en particular.

Depende de la clase implementar el método de interfaz como lo hará, dentro de los límites del contrato: virtual o "no virtual" (virtual sellado, según parece).

Jason Williams
fuente
todos en este hilo saben para qué sirven las interfaces. La pregunta es extremadamente específica: el IL generado es virtual para el método de interfaz y no virtual para un método que no es de interfaz.
Rex M
5
Sí, es muy fácil criticar una respuesta después de editar la pregunta, ¿no es así?
Jason Williams
0

Las interfaces son un concepto más abstracto que las clases, cuando declaras una clase que implementa una interfaz, simplemente dices "la clase debe tener estos métodos particulares de la interfaz, y no importa si estática , virtual , no virtual , anulada , como siempre que tenga el mismo ID y los mismos parámetros de tipo ".

Otros lenguajes que admiten interfaces como Object Pascal ("Delphi") y Objective-C (Mac) no requieren que los métodos de interfaz se marquen como virtuales y no virtuales.

Pero, puede que tenga razón, creo que puede ser una buena idea tener un atributo "virtual" / "override" específico en las interfaces, en caso de que desee restringir los métodos de clases que implementan una interfaz en particular. Pero, eso también significa tener palabras clave "no virtual", "dontcareifvirtualornot", para ambas interfaces.

Entiendo su pregunta, porque veo algo similar en Java, cuando un método de clase tiene que usar "@virtual" o "@override" para estar seguro de que un método está destinado a ser virtual.

umlcat
fuente
@override en realidad no cambia el comportamiento del código ni altera el código de bytes resultante. Lo que hace es indicar al compilador que el método así decorado está destinado a ser una invalidación, lo que permite al compilador realizar algunas comprobaciones de cordura. C # funciona de manera diferente; overridees una palabra clave de primera clase en el propio idioma.
Robert Harvey