2012-05-25 12 views
5

Me encontré con este extraño caso ayer, donde t as D devuelve un valor no nulo, pero (D)t provoca un error de compilación.¿Por qué este molde no es válido cuando `x as Y` funciona bien?

Desde que estaba en un apuro que acabo de utilizar t as D y continué, pero tengo curiosidad acerca de por qué el reparto no es válido, ya que realmente es un tD. ¿Alguien puede arrojar algo de luz sobre por qué al compilador no le gusta el elenco?

class Program 
{ 
    public class B<T> where T : B<T> { } 

    public class D : B<D> { public void M() { Console.Out.WriteLine("D.M called."); } } 

    static void Main() { M(new D()); } 

    public static void M<T>(T t) where T : B<T> 
    { 
     // Works as expected: prints "D.M called." 
     var d = t as D; 
     if (d != null) 
      d.M(); 

     // Compiler error: "Cannot cast expression of type 'T' to type 'D'." 
     // even though t really is a D! 
     if (t is D) 
      ((D)t).M(); 
    } 
} 

EDIT: Jugando, creo que este es un ejemplo más claro. En ambos casos, t tiene un límite de B y es quizás D. Pero el caso con el genérico no se compilará. ¿El C# simplemente ignora la restricción genérica al determinar si el elenco es legal? Incluso si lo ignora, t podría ser un D; Entonces, ¿por qué es esto un error de tiempo de compilación en lugar de una excepción de tiempo de ejecución?

class Program2 
{ 
    public class B { } 

    public class D : B { public void M() { } } 

    static void Main() 
    { 
     M(new D()); 
    } 

    public static void M(B t) 
    { 
     // Works fine! 
     if (t is D) 
      ((D)t).M(); 
    } 

    public static void M<T>(T t) where T : B 
    { 
     // Compile error! 
     if (t is D) 
      ((D)t).M(); 
    } 
} 
+2

Bet '(D.) (objeto) t' works –

+0

posible duplicado de [El valor del tipo 'T' no se puede convertir a] (http://stackoverflow.com/questions/4092393/value-of-type-t-cannot-be-converted- a) –

+1

De un comentario en [este enlace] (http://stackoverflow.com/questions/1613314/generic-type-casting-method-net) Encontré un enlace a [su respuesta] (http: // bl ogs.msdn.com/b/ericlippert/archive/2009/03/19/representation-and-identity.aspx). Básicamente, existen algunos tipos de variables que no pueden transferirse a otros tipos (más específicamente, existen reglas sobre el casting de tipos encuadrados). Como el compilador no tiene idea de qué es T en el momento de la compilación, tiene que ir a lo seguro y rechazar el reparto. –

Respuesta

3

En el segundo ejemplo se puede cambiar

((D)t).M(); 

a

((D)((B)t)).M(); 

se puede transmitir desde B a D, pero no necesariamente se puede emitir desde "algo que es un B" a D. "Algo que es un B" podría ser un A, por ejemplo, si A : B.

El error del compilador se produce cuando potencialmente está saltando de un niño a otro en la jerarquía. Puedes subir y bajar la jerarquía, pero no puedes cruzarla.

((D)t).M();  // potentially across, if t is an A 
((D)((B)t)).M(); // first up the hierarchy, then back down 

Nótese también que es posible que aún conseguir un error de ejecución con ((D)((B)t)).M(); si t no es en realidad un D. (Su cheque is debe evitar esto, sin embargo.)

(Observe también que en ningún caso es el compilador teniendo en cuenta el cheque if (t is D).)

Un último ejemplo:

class Base { } 
class A : Base { } 
class C : Base { } 

... 
A a = new A(); 
C c1 = (C)a;   // compiler error 
C c2 = (C)((Base)a); // no compiler error, but a runtime error (and a resharper warning) 

// the same is true for 'as' 
C c3 = a as C;   // compiler error 
C c4 = (a as Base) as C; // no compiler error, but always evaluates to null (and a resharper warning) 
+0

Entonces, ¿por qué compila 'as'? ¿Cuál es la diferencia entre cast y 'as' en este caso (_beside_ su comportamiento en tiempo de ejecución)? –

+0

'as' da el mismo error en la misma situación. EDITAR: en realidad, en el ejemplo del OP no es así. Tendrá que jugar con eso un poco más. –

+0

'as' es un animal completamente diferente. Ver [este enlace msdn] (http://msdn.microsoft.com/en-us/library/cscsdfbt%28v=vs.80%29.aspx) - es equivalente a '¿la expresión es tipo? (tipo) expresión: (tipo) null' –

1

cambiarlo a

public static void M<T>(T t) where T : D 

Ésta es la restricción apropiada si desea ordenar que T tiene que ser de tipo D.

No se compilará si su restricción define T como D de T.

Ex: no se puede emitir List<int> to int o viceversa

+0

No pregunta cómo solucionarlo, pero por qué es rechazado por el compilador. – Stilgar

+0

Respuesta actualizada. – TGH

0

Su función de plantilla tiene una restricción que requiere que T sea B<T>.

Por lo tanto, cuando el compilador intente convertir el objeto t de tipo T en , no puede hacerlo. Porque T está garantizado como B<T>, pero no D.

Si agrega una restricción para requerir que T es , esto funcionará. es decir, where T: B<T>, D o simplemente where T: D que también garantiza que T es B<T>, debido a la cadena de herencia.

Y la segunda parte de la pregunta: cuando llamas al t as D, se comprueba en el tiempo de ejecución. Y, en tiempo de ejecución, al usar polimorfismo, se verifica que t se puede convertir al tipo D, y está hecho sin errores.

NOTA: por cierto, este código es taaaan extraño. ¿Estás seguro de lo que estás haciendo?

+1

los moldes también se verifican en tiempo de ejecución, en muchos casos –

Cuestiones relacionadas