2011-11-17 10 views
9

Duplicar posible:
Compile-time and runtime casting c#¿por qué el compilador de C# coger un InvalidCastException

Según tengo entendido, el siguiente código siempre de compilación, y lo hará, además, siempre falla en tiempo de ejecución lanzando un InvalidCastException.

Ejemplo:


public class Post { } 
public class Question : Post { } 
public class Answer : Post 
{ 
    public void Fail() 
    { 
     Post p = new Post(); 
     Question q = (Question)p; // This will throw an InvalidCastException 
    } 
} 

Mis preguntas son ...

  1. Si mis suposiciones son apagado, entonces alguien puede proporcionar un ejemplo que demuestra la forma en que están fuera?
  2. Si mis suposiciones son correctas, ¿por qué el compilador no advierte contra este error?
+4

¿Por qué se puede esperar que el compilador siga todas las posibles rutas de código para determinar que 'p 'no ha sido cambiado antes del reparto? –

+0

Y si el evento no ha cambiado, Post puede implementar un operador implícito para lanzarse a Question o viceversa. – PVitt

+4

los moldes son para vaqueros, subirse al toro y montar a bebés – kenny

Respuesta

14

Hay un par de razones por las que se permite esta conversión.

Primero, como se ha dicho en otras respuestas, el operador del reparto quiere decir "Sé más que usted; le garantizo que esta conversión tendrá éxito y si me equivoco, ejecute una excepción y bloquee el proceso". Si le mientes al compilador, las cosas malas van a suceder; de hecho es no haciendo esa garantía, y el programa es bloqueándose como resultado.

Ahora, si el compilador puede decir que lo está mintiendo, entonces puede atraparlo en la mentira. ¡No es necesario que el compilador sea arbitrariamente listo para atraparte con tus mentiras! El análisis de flujo necesario para determinar que una expresión de tipo Base es nunca va a ser del tipo Derivado es complejo; considerablemente más complejo que la lógica que ya implementamos para detectar cosas como variables locales sin asignar. Tenemos mejores formas de gastar nuestro tiempo y esfuerzo que mejorar la capacidad del compilador para atraparlo en obvias mentiras.

El compilador tanto típicamente razones que sólo alrededor tipos de expresiones, no sobre los valores posibles . Únicamente desde el análisis de tipo es imposible saber si la conversión tendrá éxito o no. Es podría tener éxito, y entonces está permitido. Los únicos bloqueos que no se permiten son los que el compilador sabe siempre fallan del análisis de tipo.

segundo lugar, es posible decir (Derived)(new Base()) donde derivada es un tipo que implementa el tipo bajo y tienen que no fallan en tiempo de ejecución. ¡También es posible que (Base)(new Base()) falle con una excepción de conversión no válida en el tiempo de ejecución! ¡Hechos reales! Estas son situaciones extraordinariamente raras, pero son posibles.

Para más detalles, ver mis artículos sobre el tema:

http://blogs.msdn.com/b/ericlippert/archive/2007/04/16/chained-user-defined-explicit-conversions-in-c.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/04/18/chained-user-defined-explicit-conversions-in-c-part-two.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/04/20/chained-user-defined-explicit-conversions-in-c-part-three.aspx

11

A Post podría, en algunos casos, ser lanzado a Question. Al realizar el reparto, le dices al compilador: "Esto funcionará, lo prometo. Si no lo haces, puedes lanzar una excepción de conversión no válida".

Por ejemplo, el código que funciona bien:

Post p = new Question(); 
    Question q = (Question)p; 

Un elenco está declarando expresamente que usted sabe mejor que el compilador lo que esto realmente es. Es posible que desee hacer algo como las palabras clave as o is?

+0

Agregar plátanos a mi respuesta me hizo perder momentos preciosos, y ha sido más rápido. +1 de mi parte –

+0

@PaoloTedesco Aunque me gusta tu ejemplo de banana. – McKay

6

Cuando haces un lanzamiento explícito, le estás diciendo al compilador "Sé algo que no sabes".

usted esencialmente anular la lógica normal del compilador - ppodría ser un Question (así, el compilador compilará), le está diciendo al compilador que sabe es (a pesar de que ISN' t, por lo tanto, excepción de tiempo de ejecución).

8

El punto es que p podría ser un, ya que la pregunta hereda de Post.
considerar lo siguiente:

public class Post { } 
public class Question : Post { } 
public class Banana { } 

static class Program { 
    public static void Main(params string[] args) { 
     Post p = new Question(); 
     Question q = (Question)p; // p IS a Question in this case 
     Banana b = (Banana)p; // this does not compile 
    } 
} 
2

1) Su hipótesis está apagado. Siempre hay alguien que podría implementar un operador de conversión explícita para la pregunta convertir de publicación:

public class Question` 
{ 
    // some class implementation 

    public static explicit operator Question(Post p) 
    { 
     return new Question { Text = p.PostText }; 
    } 
} 

2) una conversión explícita es su manera de decirle al compilador que usted sabe mejor que lo hace. Si desea utilizar algo cuando no esté seguro de si un envío tendrá éxito o no y no desea una excepción en tiempo de ejecución, use los operadores is y as.

+0

¿No obtendría "las conversiones definidas por el usuario hacia o desde una clase base no están permitidas"? –

+0

El autor de la pregunta ya ha indicado el contenido de las clases (vacías), por lo tanto, esta respuesta no es válida. Ni siquiera los mostró parciales ni nada, así que, "estrictamente" hablando, este no se aplica. – Meligy

+0

@MohamedMeligy - El OP puede haber mostrado sus implementaciones ... pero al compilador no le importa. Todavía existe la posibilidad de que exista una operación de conversión explícita y el compilador no vaya a verificar. –

0

Sus suposiciones son correctas: se compilará y fallará en el tiempo de ejecución.

En su pequeño ejemplo, es obvio que el molde fracasará, pero el compilador no tiene manera de saberlo. Como Post es un supertipo de Question, puede asignar Question a p, y como hace el reparto, declara que está dispuesto a asumir cierta responsabilidad por parte del compilador. Si intentas asignar un string o alguna otra cosa que no forme parte de la misma rama de herencia, el compilador debería advertirte. Por el contrario, siempre puedes tratar de convertir object a cualquier tipo.

Pero tener el compilador quejándose de su ejemplo específico significaría que no se permitirían lanzamientos.

1

El compilador trata a p como una variable, por lo tanto, no intenta rastrear su valor. Si lo hiciera, tomaría tanto tiempo analizar toda la aplicación. Algunas herramientas de análisis estático le gustan FxCop.

El compilador ve una Post, pero no siguió la asignación, y se sabe que a lo mejor:

Post p = new Question(); 

Así, pasa normalmente.

usted sabe que no puede hacer:

Question q = p; 

La diferencia está en éste que está tratando de decirle al compilador que use lo que sabe para validar esto, y se conoce el Post no es necesariamente una Question.

En la versión original le está diciendo al compilador "Sé que lo es, y lo estableceré explícitamente, salga de mi camino y tomaré la excepción si lo que sé es incorrecto", por lo tanto, escucha usted y se sale de su camino!

0

¡Wow Jeremy, me encontré con este problema exacto recientemente! Así que hice este práctico método de extensión que mapea dos modelos que comparten algunas propiedades idénticas. La intención era usarlo cuando la clase A hereda de la clase B para asignar la clase B a la clase A. ¡Espero que les resulte útil!

public static class ObjectHelper 
{ 
    public static T Cast<T>(this Object source) 
    { 
     var destination = (T)Activator.CreateInstance(typeof(T)); 

     var sourcetype = source.GetType(); 
     var destinationtype = destination.GetType(); 

     var sourceProperties = sourcetype.GetProperties(); 
     var destionationProperties = destinationtype.GetProperties(); 

     var commonproperties = from sp in sourceProperties 
           join dp in destionationProperties on new { sp.Name, sp.PropertyType } equals 
            new { dp.Name, dp.PropertyType } 
           select new { sp, dp }; 

     foreach (var match in commonproperties) 
     { 
      match.dp.SetValue(destination, match.sp.GetValue(source, null), null); 
     } 

     return destination; 
    } 
} 

FYI, probablemente solo funcione si los dos objetos existen en el mismo conjunto.

Gran parte del código de vino de aquí: Mapping business Objects and Entity Object with reflection c#

Cuestiones relacionadas