2012-02-08 9 views
59

El siguiente código le da un error de compilación, como era de esperar:¿Por qué el compilador de C# permite un molde explícito entre IEnumerable <T> y TAlmostAnything?

List<Banana> aBunchOfBananas = new List<Banana>(); 

Banana justOneBanana = (Banana)aBunchOfBananas; 

Sin embargo, cuando se utiliza IEnumerable<Banana>, que sólo obtiene un error de tiempo de ejecución.

IEnumerable<Banana> aBunchOfBananas = new List<Banana>(); 

Banana justOneBanana = (Banana)aBunchOfBananas; 

¿Por qué el compilador de C# lo permite?

+0

una lista de lo que no es una sola cosa. –

+2

No está permitido con tipos de valores, sin embargo. – ken2k

+1

Esto también manzanas (vale) para IList . Eso es lo que hizo que toda la interfaz frente a la clase concreta haga clic para mí en las respuestas a continuación. – deepee1

Respuesta

48

yo supongo que es porque IEnumerable<T> es una interfaz donde alguna aplicación podría tienen una conversión explícita a Banana - no importa lo tonto que sería.

Por otro lado, el compilador sabe que List<T> no se puede convertir explícitamente en Banana.

¡Buena elección de ejemplos, por cierto!

Agregando un ejemplo para aclarar. Tal vez tendríamos un poco de "numerable" que siempre debe contener como máximo una sola Banana:

public class SingleItemList<T>:Banana, IEnumerable<T> where T:Banana { 
    public static explicit operator T(SingleItemList<T> enumerable) { 
     return enumerable.SingleOrDefault(); 
    } 

    // Others omitted... 
} 

Entonces podría realmente hacer esto:

IEnumerable<Banana> aBunchOfBananas = new SingleItemList<Banana>(); 
Banana justOneBanana = (Banana)aBunchOfBananas; 

ya que es el mismo que escribiendo lo siguiente, que el compilador está perfectamente satisfecho con:

Banana justOneBanana = aBunchOfBananas.SingleOrDefault(); 
+4

No puede hacer esto, no encontrará el operador. Un ejemplo sería 'clase SingleBanana: Banana, IEnumerable '. Esta es la razón por la cual esto solo funciona para las interfaces, porque si ambos tipos son clases, se puede determinar que esto es imposible en el momento de la compilación. – Random832

+0

@ Random832 Buena captura, pero de hecho su firma de clase tampoco funcionará (no puede tener una conversión explícita a una clase base). Actualicé mi respuesta con una versión que funciona según lo previsto. – Yuck

+0

Er, mi punto era que no se puede usar la sobrecarga del operador _at all_ por esto. La razón por la que funciona es porque un objeto de clase derivado es _inherentemente_ convertible a la clase base. – Random832

16

Puede ser porque el compilador sabe que Banana no se extiende List<T>, pero que existe la posibilidad de que algún objeto que implementa IEnumerable<T> también se extienda a Banana y lo convierta en un modelo válido.

+1

Es triste ver la única respuesta correcta, publicada entre los primeros, no obtener tantos votos hacia arriba. Tal vez un breve fragmento de código ayudaría. –

29

Cuando dice Y y = (Y)x; este elenco dice al compilador "confía en mí, lo que es x, en tiempo de ejecución que se puede lanzar a un Y, así, sólo lo hacen, ¿de acuerdo?"

Pero cuando se dice

List<Banana> aBunchOfBananas = new List<Banana>(); 
Banana justOneBanana = (Banana)aBunchOfBananas; 

el compilador puede mirar las definiciones de cada una de estas clases concretas (Banana y List<Banana>) y ver que no hay static explicit operator Banana(List<Banana> bananas) definido (recuerda, una conversión explícita se debe definir ya sea en el tipo que se está fundiendo o en el tipo que se va a transmitir, esto es de la especificación, sección 17.9.4). Sabe en tiempo de compilación que lo que dices nunca puede ser cierto. Entonces te grita que dejes de mentir.

Pero cuando se dice

IEnumerable<Banana> aBunchOfBananas = new List<Banana>(); 
Banana justOneBanana = (Banana)aBunchOfBananas; 

bueno, ahora el compilador no sabe. Muy bien podría darse el caso de que cualquiera que sea aBunchOfBananas que se encuentre en tiempo de ejecución, su tipo concreto X podría haber definido static explicit operator Banana(X bananas). Entonces el compilador confía en ti, como tú lo pediste.

+0

¿Esta fue downvoted? – jason

+0

Tal vez porque no lo mencionó, ¿hace una distinción entre clases y números enteros? Sin saber eso, su primer (X e Y) y segundo ejemplo son exactamente lo mismo. –

+1

Sí esto es downvoted ... está mal. Los operadores de conversión no se pueden encontrar en tiempo de ejecución (a menos que se involucre la palabra clave 'dynamic'). Tener otro voto negativo. –

0

De acuerdo con las especificaciones idioma (6.2.4) "Las conversiones explícitas de referencia son: Desde cualquier tipo de clase-S a cualquier tipo de interfaz T, siempre y S no se sella y se proporcionó S no implementa T .. Las conversiones de referencia explícitas son aquellas conversiones entre tipos de referencia que requieren verificaciones en tiempo de ejecución para garantizar que son correctas ... "

El compilador no comprueba la realización de la interfaz durante la compilación. Hace CLR en tiempo de ejecución. Comprueba los metadatos que intentan encontrar la realización en clase o entre sus padres. No sé por qué se comporta así. Probablemente lleva mucho tiempo. Por lo que este código se compila correctamente:

public interface IInterface 
{} 

public class Banana 
{ 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Banana banana = new Banana(); 

     IInterface b = (IInterface)banana; 
    } 
} 

En otra parte si tratamos de echar el plátano a la clase, el compilador comprueba sus metadatos y lanzar un error:

FileStream fs = (FileStream)banana; 
Cuestiones relacionadas