2011-11-17 13 views
9

Eventuali duplicati:
Compile-time and runtime casting c#Perché non il compilatore C# prendere un InvalidCastException

Da quanto ho capito, il seguente codice sempre di compilazione, e sarà inoltre sempre non riesce in fase di esecuzione lanciando un InvalidCastException.

Esempio:


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 
    } 
} 

Le mie domande sono ...

  1. Se le mie supposizioni sono spenti, allora qualcuno può fornire un esempio che illustra come sono fuori?
  2. Se le mie supposizioni sono corrette, allora perché il compilatore non mette in guardia contro questo errore?
+4

perché si aspetta il compilatore di seguire tutti i percorsi di codice possibile determinare che 'p 'non è stato cambiato prima del cast? –

+0

E se l'evento non è stato modificato, Post può implementare un operatore implicito per eseguire il cast stesso su Domanda o viceversa. I calchi – PVitt

+4

sono per i cowboy, salire sul toro e cavalcare il bambino – kenny

risposta

14

Ci sono un paio di ragioni per le quali è consentita questa conversione.

In primo luogo, come hanno detto altre persone in altre risposte, l'operatore del cast significa "So più di te, ti garantisco che questa conversione avrà esito positivo e se sbaglio, lancia un'eccezione e interrompi il processo". Se stai mentendo al compilatore, le cose brutte stanno per accadere; in effetti si tratta di non che fa la garanzia, e il programma è arresto anomalo di conseguenza.

Ora, se il compilatore può dire che stai mentendo su di esso, allora può prenderti nella bugia. Il compilatore non è tenuto ad essere arbitrariamente intelligente nel prenderti nelle tue bugie! L'analisi del flusso necessaria per determinare che un'espressione di tipo Base sia mai che sarà di tipo Derivato è complessa; notevolmente più complesso della logica che già implementiamo per catturare cose come variabili locali non assegnate. Abbiamo modi migliori di impiegare il nostro tempo e la nostra fatica piuttosto che migliorare la capacità del compilatore di catturarti in bugie ovvie.

Il compilatore pertanto ragiona tipicamente solo circa tipi di espressioni, non di valori possibili. Solamente dall'analisi del tipo è impossibile sapere se la conversione avrà successo. E 'potrebbe riuscire, e quindi è permesso. Gli unici cast non consentiti sono quelli che il compilatore sa sarà sempre fallire dall'analisi del tipo.

secondo luogo, è possibile dire (Derived)(new Base()) dove derivato è un tipo che implementa tipo di base e lo hanno non non riescono a runtime. È anche possibile che (Base)(new Base()) fallisca con un'eccezione di cast non valida in fase di runtime! Fatti veri! Queste sono situazioni straordinariamente rare ma sono possibili .

Per maggiori dettagli, vedere i miei articoli sul 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 è possibile trasmettere in alcuni casi a Question. Eseguendo il cast, stai dicendo al compilatore: "Funzionerà, lo prometto, se non ti è permesso lanciare un'eccezione cast non valida".

Ad esempio, questo codice dovrebbe funzionare bene:

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

Un cast è espressamente affermando che lo sai meglio di ciò che il compilatore questo in realtà è. Potresti voler fare qualcosa come le parole chiave as o is?

+0

L'aggiunta di banane alla mia risposta mi ha fatto perdere momenti preziosi, e tu sei stato più veloce. +1 da me. –

+0

@PaoloTedesco Mi piace il tuo esempio di banana però. – McKay

6

Quando si esegue un cast esplicito, si sta dicendo al compilatore "So qualcosa che non si fa".

Sei in sostanza sovrascrivendo la normale logica del compilatore - ppotrebbe essere un Question (così, il compilatore compilerà), si indica al compilatore che si sa è (anche se esso isn' t, quindi eccezione di runtime).

8

Il punto è che p potrebbe essere un Question, poiché la domanda eredita da Post.
Si consideri il seguente:

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) La tua ipotesi è spento. Qualcuno potrebbe sempre implementare un operatore di conversione esplicita per la domanda convertire da Messaggio:

public class Question` 
{ 
    // some class implementation 

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

2) un cast esplicito è il tuo modo di dire al compilatore che lo sai meglio di quanto non faccia. Se vuoi qualcosa da usare quando non sei sicuro che un cast abbia successo o meno e non vuoi un'eccezione di runtime, usa gli operatori is e as.

+0

Non otterresti "le conversioni definite dall'utente in o da una classe base non sono consentite"? –

+0

Lo scrittore della domanda ha già dichiarato il contenuto delle classi (vuoto), quindi questa risposta non è valida. Non li ha nemmeno mostrati parziali o nulla, quindi, "rigorosamente" parlando, questo non si applica. – Meligy

+0

@MohamedMeligy - L'OP potrebbe aver mostrato le sue implementazioni ... ma al compilatore non interessa. C'è ancora la possibilità che esista un'operazione di conversione esplicita e il compilatore non sta andando a controllare. –

0

Le ipotesi sono corrette: verrà compilata e avrà esito negativo in fase di esecuzione.

Nel tuo piccolo esempio è ovvio che il cast fallirà, ma il compilatore non ha modo di saperlo. Dato che Post è un supertipo di Question, è possibile assegnare uno Question a p e, poiché si esegue il cast, si dichiara di volersi prendere una certa responsabilità dal compilatore. Se stavi cercando di assegnare uno string o qualcos'altro che non fa parte dello stesso ramo di ereditarietà, il compilatore dovrebbe avvisarti. Al contrario, puoi sempre provare a trasmettere object a qualsiasi tipo.

Ma avere il compilatore che si lamenta del proprio esempio specifico significherebbe che non sarebbe consentito alcun cast.

1

Il compilatore considera p come una variabile, quindi non tenta di tracciare il suo valore. Se lo facesse, ci sarebbe voluto così tanto tempo per analizzare l'intera applicazione. Alcuni strumenti di analisi statica fanno come FxCop.

il compilatore vede una Post, ma non ha traccia l'assegnazione, e sa che forse:

Post p = new Question(); 

Quindi, passa normalmente.

sai che non si può fare:

Question q = p; 

La differenza è in questo che si sta cercando di dire al compilatore di utilizzare ciò che sa per convalidare questo, e conosce la Post non è necessariamente una Question.

Nella versione originale si sta dicendo al compilatore "Lo so, lo imposto esplicitamente, togli l'apparecchio e farò l'eccezione se quello che so è sbagliato", quindi ascolta tu e togliti!

0

Wow Jeremy, mi sono imbattuto in questo preciso problema di recente! Così ho creato questo pratico metodo di estensione che associa due modelli che condividono alcune proprietà identiche. L'intenzione era di usarla quando la classe A eredita dalla classe B per mappare la classe B alla classe A. Spero che tu la trovi utile!

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, probabilmente funzionerà solo se i due oggetti sono presenti nello stesso assieme.

maggior parte del codice è venuto da qui: Mapping business Objects and Entity Object with reflection c#

Problemi correlati