C#: propiedad que invalida especificando la interfaz explícitamente
interface override (1)
Al intentar anular la implementación de la interfaz explícita de la propiedad ICollection<T>.IsReadOnly
de la clase Collection<T>
, encontré algunos documentos que indican que las implementaciones de miembros de la interfaz explícita no se pueden anular porque no pueden tener modificadores como virtual
o abstract
. En MSDN incluso llegan a especificar cómo hacer que una implementación de miembro de interfaz explícita esté disponible para la herencia mediante la creación de otro miembro abstracto o virtual al que se llama la implementación de miembro de interfaz explícita. No hay problemas hasta ahora.
Pero luego me pregunto: ¿Por qué es posible en C # anular cualquier miembro de la interfaz implementado explícitamente simplemente especificando la interfaz explícitamente ?
Por ejemplo, supongamos que tengo una interfaz simple como esta, con una propiedad y un método:
public interface IMyInterface
{
bool AlwaysFalse { get; }
bool IsTrue(bool value);
}
Y una clase A
que implementa la interfaz explícitamente, y tiene un método Test()
que llama a su propia implementación de miembro de la interfaz.
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 puede ver, ninguno de los cuatro miembros es virtual o abstracto, así que cuando defino una clase B
como esta:
public class B : A
{
public bool AlwaysFalse
{ get { return true; } }
public bool IsTrue(bool value)
{ return !value; }
}
Entonces esperarías que una instancia de B
convertida en A
comporte 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. Cree 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 lanza a A
, no se comporta como A
sino 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?
Esto no tiene nada que ver con la implementación explícita de la interfaz; es simplemente una consecuencia de las reglas generales de herencia y asignación de interfaz: vería exactamente los mismos resultados si el tipo A
proporcionara una implementación implícita, en lugar de explícita, de IMyInterface
.
- El tipo
B
hereda del tipoA
Nada está anulado.
B
proporciona sus propios miembrosAlwaysFalse
eIsTrue
pero no implementanIMyInterface
; La implementación deIMyInterface
es proporcionada por los miembros heredados deA
: cuando una instancia de tipoB
seIMyInterface
enIMyInterface
, se comporta exactamente de la misma manera que en una instancia de tipoA
porqueA
proporciona los miembros que implementan la interfaz. - El tipo
C
hereda del tipoA
Una vez más, nada está anulado.
C
proporciona sus propios miembrosAlwaysFalse
eIsTrue
, pero esta vez esos miembros implementanIMyInterface
: cuando una instancia de tipoC
seIMyInterface
enIMyInterface
, los miembros deC
proporcionan la implementación de la interfaz en lugar deA
Debido a que el tipo A
implementa IMyInterface
explícitamente, el compilador no advierte que los miembros de B
y C
están ocultando a los miembros de A
; En efecto, esos miembros de A
ya estaban ocultos debido a la implementación de la interfaz explícita.
Si cambió el tipo A
para implementar IMyInterface
implícitamente en lugar de explícitamente, entonces el compilador advertirá que los miembros de B
y C
están ocultando, no anulando, los miembros de A
y que idealmente debería usar el new
modificador cuando declare esos 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 la especificación ECMA-334 ; secciones 13.4.4 y 13.4.6 en la especificación Microsoft C # 4 ).
20.4.2 Mapeo de la interfaz
La implementación de un
IM
miembro de interfaz particular, dondeI
es la interfaz en la que se declara el miembroM
, se determina examinando cada clase o estructuraS
, comenzando conC
y repitiendo para cada clase base sucesiva deC
, hasta que se localice una coincidencia .20.4.4 Reimplementación de la interfaz
Se permite que una clase que herede una implementación de interfaz vuelva a implementar la interfaz al incluirla en la lista de clases base. Una reimplementación de una interfaz sigue exactamente las mismas reglas de asignación de interfaz que una implementación inicial de una interfaz. Por lo tanto, la asignación de interfaz heredada no tiene ningún efecto en la asignación de interfaz establecida para la reimplementación de la interfaz.