16

Al intentar anular la implementación de interfaz explícita de la propiedad de la clase Collection<T>, encontré algunos documentos que indican que las implementaciones explícitas de miembros de la interfaz no pueden anularse porque no pueden tener modificadores como virtual o abstract. En MSDN llegan incluso a especificar cómo hacer que una implementación de miembro de interfaz explícita esté disponible para la herencia creando otro miembro abstracto o virtual al que llama la implementación de miembro de interfaz explícita. Sin problemas hasta ahora.C#: sobrescritura de propiedades especificando la interfaz explícitamente

Pero entonces me pregunto: Por qué es posible en C# para anular cualquier miembro de interfaz implementada de manera explícita simplemente especificando la interfaz explícitamente?

Por ejemplo, supongamos que tengo una interfaz sencilla como esta, con una propiedad y método:

public interface IMyInterface 
{ 
    bool AlwaysFalse { get; } 
    bool IsTrue(bool value); 
} 

y una clase A que implementa la interfaz de forma explícita, y tiene un método Test() que llama a su propia interfaz implementación de miembro.

public class A : IMyInterface 
{ 
    bool IMyInterface.AlwaysFalse 
    { get { return false; } } 

    bool IMyInterface.IsTrue(bool value) 
    { return value; } 

    public bool Test() 
    { return ((IMyInterface)this).AlwaysFalse; } 
} 

Como se puede ver, ninguno de los cuatro miembros son virtuales o abstractos, así que cuando me defino una clase B así:

public class B : A 
{ 
    public bool AlwaysFalse 
    { get { return true; } } 

    public bool IsTrue(bool value) 
    { return !value; } 
} 

Entonces lo que espera una instancia de yeso para BA para comportarse como A. Y lo hace:

A a = new A(); 
Console.WriteLine(((IMyInterface)a).AlwaysFalse); // False 
Console.WriteLine(((IMyInterface)a).IsTrue(false)); // False 
Console.WriteLine(a.Test());       // False 
A b = new B(); 
Console.WriteLine(((IMyInterface)b).AlwaysFalse); // False 
Console.WriteLine(((IMyInterface)b).IsTrue(false)); // False 
Console.WriteLine(b.Test());       // False 

Ahora viene la trampa. Crear una clase C que es una copia exacta de B excepto por una cosa en la declaración de clase:

public class C : A, IMyInterface 
{ /* ... same as B ... */ } 

Ahora una instancia de C, cuando se convierte a A, no se comportan como A pero como C:

A c = new C(); 
Console.WriteLine(((IMyInterface)c).AlwaysFalse); // True 
Console.WriteLine(((IMyInterface)c).IsTrue(false)); // True 
Console.WriteLine(c.Test());       // True 

¡Incluso el método Test() ahora llama al método anulado en C! ¿Por qué es esto?

+0

pregunta muy interesante! –

Respuesta

10

Esto tiene nada que ver con la implementación de interfaz explícita; es simplemente una consecuencia de las reglas generales de asignación de la herencia y la interfaz: se podría ver exactamente los mismos resultados si el tipo A proporcionó una implícita, en lugar de explícita, la implementación de IMyInterface.

  • Tipo B hereda de tipo A. Nada es anulado.
    B proporciona su propio AlwaysFalse y IsTrue miembros pero No implemento IMyInterface; La implementación de IMyInterface es proporcionada por los miembros heredados de A: cuando una instancia de tipo B se convierte a IMyInterface, se comporta exactamente de la misma manera que una instancia de tipo A porque A proporciona los miembros que implementan la interfaz.
  • Tipo C hereda de tipo A. De nuevo, nada está anulado.
    C proporciona su propio AlwaysFalse y IsTrue miembros, pero esta vez lo hacen los miembros implemento IMyInterface: cuando una instancia de tipo C se echó a IMyInterface entonces los miembros de C proporcionan la interfaz de la aplicación en lugar de las de A.

Debido tipo A implementos IMyInterface explícitamente el compilador no advierten que los miembros de B y C se esconden los miembros de A; En efecto, los miembros de A ya estaban ocultos debido a la implementación explícita de la interfaz.

Si ha cambiado el tipo A para implementar IMyInterface implícitamente más que explícitamente a continuación, el compilador advertir que los miembros de B y C se esconden, no de primer orden, los miembros de A y que lo ideal sería utilizar el modificador new al declarar los miembros en B y C.

Aquí hay algunos bits relevantes de la especificación del lenguaje. (Secciones 20.4.2 y 20.4.4 en el ECMA-334 spec; Secciones 13.4.4 y 13.4.6 en el Microsoft C#4 spec.)

20.4.2 Interfase de Mapeo

La implementación de una interfaz particular miembro de I.M, donde I es la interfaz en la que se declara el miembro de M , se determina por examinar cada clase o estructura S, comenzando con C y repitiendo para cada clase base sucesiva de C, hasta que se encuentre una coincidencia.

20.4.4 Interfaz reimplementación

una clase que hereda una interfaz está permitido aplicación a re-implementar la interfaz por incluirlo en la lista de la clase base. Una implementación de de una interfaz sigue exactamente las mismas reglas de asignación de la interfaz como una implementación inicial de una interfaz.Por lo tanto, la asignación de interfaz heredada no tiene ningún efecto en la interfaz asignación establecida para la reimplementación de la interfaz.

+0

Así que usted dice que es lógico y esperado que cualquiera pueda proporcionar una nueva implementación para cualquier miembro explícito de la interfaz y, sobre todo, que la clase original llame a ese nuevo miembro (el que oculta la implementación original) sin ninguna anulación, virtual o métodos abstractos en absoluto? – Virtlink

+0

Es precisamente porque * no * ignora que está viendo este comportamiento: por ejemplo, escriba 'B' proporciona una * nueva * implementación de' IsTrue' que * no * anula el método 'IsTrue' de type' A'. Tipo 'B' hereda su implementación de' IMyInterface' de 'A'. Es el método 'A.IsTrue' que implementa' IMyInterface' así que cuando lanzas una instancia de 'B' a' IMyInterface' es el método 'A.IsTrue' que se usa. [...] – LukeH

+0

El tipo 'C' también proporciona una * nueva * implementación de' IsTrue' que * no * anula el método 'IsTrue' de tipo' A'. Pero teclee 'C' implementa' IMyInterface' directamente. Cuando lanzas una instancia de 'C' a' IMyInterface', se usa el método 'C.IsTrue', de acuerdo con las reglas de la especificación del lenguaje. – LukeH

Cuestiones relacionadas