2012-09-09 79 views
8

Si intento una conversión inválida de una clase a una interfaz, entonces el compilador no se queja (el error ocurre en tiempo de ejecución); hace quejarse, sin embargo, si intento un lanzamiento similar a una clase abstracta.¿Por qué no hay error de compilación cuando lanzo una clase a una interfaz que no implementa?

class Program 
{ 
    abstract class aBaz 
    { 
     public abstract int A { get; } 
    } 

    interface IBar 
    { 
     int B { get; } 
    } 

    class Foo 
    { 
     public int C { get; } 
    } 

    static void Main() 
    { 
     Foo foo = new Foo(); 

     // compiler error, as expected, since Foo doesn't inherit aBaz 
     aBaz baz = (aBaz)foo; 

     // no compiler error, even though Foo doesn't implement IBar 
     IBar bar = (IBar)foo; 
    } 
} 

¿por qué el compilador rechazan el reparto de Foo a IBar, cuando es (aparentemente?) No es válida? O, para voltear la pregunta, si el compilador permite este lanzamiento "no válido" a la interfaz IBar, ¿por qué no permite el molde "inválido" similar a la clase abstracta aBaz?

+1

http://blogs.msdn.com/b/ericlippert/archive/2009/03/19/representation-and-identity.aspx – SLaks

+0

Este casting a una interfaz me mordió en el trasero esta noche ... y lo hizo duele. –

+1

No marcó 'Foo' como' sellado'. Por lo tanto, el reparto explícito se compilará. Podría existir una clase que hereda de 'Foo' que además implementó' IBar'. Si usa 'clase sellada Foo', el lanzamiento a' IBar' será ilegal en tiempo de compilación. (En este caso, la clase 'Foo' está anidada en privado, por lo que incluso si el molde debe permitirse, es concebible que el compilador C# pueda dar un _warning_ (no error ya que la especificación dice que existe una conversión explícita) porque puede ver el alcance completo de la clase anidada. Pero es probable que sea un caso raro.) _Editar: _ Ahora veo que Alexei lo cubre a continuación. –

Respuesta

7

Debes entender el sistema de herencia de .Net para ver por qué esto tiene sentido. En .Net, una clase puede heredar solo de una clase base pero puede implementar cualquier cantidad de interfaces.

class Program 
{ 
    abstract class aBaz 
    { 
     public abstract int A { get; } 
    } 

    interface IBar 
    { 
     int B { get; } 
    } 

    class Foo 
    { 
     public int C { get; } 
    } 

    class BarableFoo : Foo, IBar 
    { 
     public int C { get; } 
    } 

    static void Main() 
    { 
     // This is why the compiler doesn't error on the later cast 
     Foo foo = new BarableFoo(); 

     // compiler error: aBaz is a class and the compiler knows that 
     // Foo is not a _subclass_ of aBaz. 
     aBaz baz = (aBaz)foo; 

     // no compiler error: the class Foo does not implement IBar, however at runtime 
     // this instance, "foo", might be a subclass of Foo that _implements_ IBar. 
     // This is perfectly valid, and succeeds at runtime. 
     IBar bar = (IBar)foo; 

     // On the other hand... 
     foo = new Foo(); 

     // This fails at runtime as expected. 
     bar = (IBar)foo; 
    } 

} 

En el ejemplo original, extremadamente simple en la pregunta, parece que el compilador podría detectar que esta instancia de foo nunca va a poder convertir al IBar, sino que es más bien un "bueno tener" advertencia que una cuestión de corrección del lenguaje.

+1

+1. También se agregó una respuesta que muestra el caso cuando el lanzamiento falla en tiempo de compilación. –

+0

Hubiera esperado que si querías lanzar un Foo a un Bar, primero deberías lanzarlo a BarableFoo, para que quedara claro qué tipos esperabas – Andy

+0

@Andy - ¿Estás hablando de esa última línea o la medio '(IBar) foo'? – BitwiseMan

6

El objetivo de un molde es suprimir el error del compilador.
(por ejemplo, si se sabe que foo es realmente una instancia de un subtipo que no implementa la interfaz)

Si el compilador puede demostrar que es imposible que el molde tenga éxito, todavía dará un error. (por ejemplo, si lo lanzas a una clase que no se encuentra en su jerarquía)

+0

Ok ... entonces usted está diciendo que el compilador puede probar que el primer lanzamiento no es válido, pero no el segundo. Pero ¿por qué no puede probar el segundo? ¿No se debe declarar a Foo (o uno de sus antepasados) como implementador de IBar para hacer posible ese reparto? – McGarnagle

+1

@dbaseman: ¿Y si escribo 'clase Baz: Foo, IBar'? – SLaks

+1

@McGarnagle: uno de los descendientes de 'Foo' tendría que implementar' IBar' para hacer posible el molde, pero en muchos casos no hay forma de que el compilador sepa si un ensamblado contiene algún descendiente de 'Foo' que implemente' IBar' podría ser cargado. – supercat

5

Y para demostrar que el compilador no es tonto no es el caso actulaly cuando conversión fallará en tiempo de compilación: si compilador puede probar que no hay otras clases pueden derivar de la clase se producirá un error de lanzamiento a las interfaces en tiempo de compilación:

sealed class NoBar 
{ 
} 

struct NoBarValue 
{ 
} 

IBar noBar = (IBar)(new NoBar()); // fails at compile time 
IBar noBarValue = (IBar)(new NoBarValue()); // fails at compile time 

En el primer caso (NoBar) la clase está explícitamente sellada (y por lo tanto no hay clases derivadas que puedan implementar IFoo) y el compilador sabe que no implementa IBar, por lo que puede fallar en tiempo de compilación. El segundo caso (NoBarValue) es similar, con la única diferencia de que los tipos de valores (struct) están sellados implícitamente.

+0

Agradable además, gracias. – McGarnagle

Cuestiones relacionadas