2010-10-25 8 views
22

Questa è una domanda per principianti, ma mi interessa sapere cosa sta succedendo qui. La mia domanda è: cosa succede dietro le quinte quando lanci un oggetto? Mantiene una sorta di metadati su ciò che era in origine? Ecco cosa voglio dire:Quando un cast viene lanciato su una classe base, come ricorda cosa è realmente?

Supponiamo che io sono un metodo chiamato "clockin" che accetta un parametro di tipo "Dipendente":

public static void ClockIn(Employee employee) 
{ 
    var manager = employee as Manager; 
    if (manager != null) 
    { 
     manager.OpenSafe(); 
    } 
} 

Quindi, assumere che Manager è una sottoclasse del tipo Employee e che ha il metodo "OpenSafe":

public class Manager : Employee 
{ 
    public void OpenSafe() 
    { 
     ... 
    } 
} 

il metodo "clockin", se rileva che un manager è stato passato in, chiama il metodo OpenSafe. Come ad esempio:

var bob = new Manager(); 
ClockIn(bob); 

Qui, ho passato in un'istanza di tipo manager in un metodo che accetta il Dipendente classe di base. Ho bisogno di lanciare l'istanza all'interno del metodo ClockIn su Manager prima di poter chiamare OpenSafe.

La domanda è, c'è qualche metadati che ricorda che "Bob" è un manager, anche se l'ho passato come un impiegato? Come fa il codice a sapere che può essere effettivamente assegnato a un manager? C'è qualcosa che sta succedendo nel mucchio?

+6

Questa non è una domanda per principianti. Scommetto che la maggior parte dei professionisti (> almeno il 95%) non lo sanno. È anche una ** ottima domanda **. –

risposta

13

La prima cosa da ricordare è che il casting non fa non modificare l'oggetto originale affatto. Cambia solo la tua vista dell'oggetto attraverso quel particolare riferimento. Più di un riferimento può puntare allo stesso oggetto, quindi cambiare l'oggetto non è una cosa ragionevole da fare su un cast.

Che cosa si potrebbe fare nel tuo caso è quello di rendere un ClockIn()metodo della classe Employee. Poi, quando si chiama

bob.ClockIn(); 

poi bob sapranno che tipo ha davvero è, e chiamare il metodo appropriato ClockIn() per il suo tipo. Questo è chiamato dynamic method dispatch e non è disponibile per le funzioni static come nell'esempio.

Ad esempio:

public class Employee { 
    public void ClockIn() { 
     .... 
    } 
} 

public class Manager: Employee { 
    public void ClockIn() { 
     // first, do what all Employees do when clocking in 
     Employee.ClockIn(); 
     // Next, do Manager specific actions 
     OpenSafe(); 
    } 
    public void OpenSafe() { 
     .... 
    } 
} 
+0

Quello che stai chiedendo non è nei metadati in sé, riguarda Polymorphism e tabelle virtuali. Puoi leggerne il succo qui: http: //www-numi.fnal.gov/offline_software/srt_public_context/WebDocs/Companion/cxx_crib/virtual_functions.html – CubanX

+0

Questo è in realtà un ottimo refactoring, Greg, anche se sono un po ' più interessato all'aspetto "cosa sta succedendo dietro le quinte" del mio esempio. Dove dici che cambia la tua vista è interessante per me. –

+2

Anche ClockIn deve essere virtuale;) –

5

Calchi non cambiano il tipo di esecuzione di un oggetto.

La chiamata del metodo GetType su un oggetto restituisce un oggetto Type che rappresenta il suo tipo di runtime.

+0

Ah. Molto interessante. Chiamare GetType sull'istanza che è stata passata a ClockIn mi dà il tipo originale. Quindi posso sicuramente vedere che il suo tipo originale è preservato, anche dopo il down-casting. –

1

tipo effettivo di oggetto è sempre memorizzato come link alla sua System.Type. In realtà ogni oggetto .NET ha un ulteriore campo System.Type che fa riferimento al suo tipo effettivo.

3

Casting non influenza l'oggetto - colpisce il riferimento all'oggetto.

Tutto quello che sta facendo quando ti abbatti un oggetto è dire al compilatore che questo oggetto può essere referenziato da una variabile che deriva dal tipo attualmente riferimento all'oggetto.

Il punto chiave è la classe dell'oggetto non cambia mai, sei solo cambiando il tipo del riferimento all'oggetto.

+0

Ora ho una visione abbastanza chiara, penso. L'oggetto, indipendentemente dal cast, mantiene sempre le informazioni di tipo originale. La variabile in cui è archiviato l'oggetto ha conoscenza per connettersi ai metodi e alle proprietà solo del tipo in cui è stata eseguita la cast. Poiché le informazioni sul tipo sono sempre intatte, posso eseguire il rendering up-cast e il membro della classe nascosto tornerà in vista. –

+0

A seconda del tipo di cast, il cast può semplicemente influenzare il modo in cui il compilatore fa riferimento al riferimento all'oggetto, oppure può creare un nuovo oggetto il cui contenuto è in qualche modo derivato dall'oggetto originale. Ci sono volte in cui questo comportamento può essere utile, ma può essere molto confuso se applicato a oggetti mutabili. – supercat

+0

@supercat: ti riferisci al cast dei tipi di valore (cioè boxing e unboxing)? –

0

Estendendo le risposte di cui sopra, è utile pensare agli aspetti pubblici di una classe come se fossero un quadro elettrico. Risolti i punti di connessione del mondo esterno collegati al funzionamento interno della classe. Se una classe eredita un'altra classe, gli oggetti della nuova classe ottengono un pannello etichettato con il loro nuovo tipo di classe, oltre ad avere un pannello del tipo di classe base. L'aggiunta di un'interfaccia aggiunge un altro pannello.

Se una proprietà o un metodo è dichiarato sostituibile, il retro della "connessione" del pannello per tale metodo/proprietà avrà una spina staccabile; altrimenti non lo farà. Supponiamo che una classe "Alpha" abbia un metodo "foo" overridable e una "barra" non sovrascrivibile. Una classe derivata "Bravo" sovrascrive "foo" e "barra" di ombre. Oggetti di classe "Bravo" avranno sia "Alpha.foo" che "Bravo.foo" collegati alla funzione "foo" di Bravo; avranno "Alpha.Bar" collegato alla funzione "Bar" di Alpha e "Bravo.Bar" collegato alla funzione "Bar" di Bravo.

Se un oggetto "BravoInstance" di tipo "Bravo" è usato in un posto dove ci si aspetta un "Bravo", un riferimento al suo "BravoInstance.Bar" farà sì che il sistema di guardare pannello e l'uso di un oggetto "Bravo" la sua connessione "Bar" (collegata a Bravo.Bar). Se una tale istanza è data al codice che si aspetta un Alpha (perfettamente ammissibile a causa dell'ereditarietà), un tentativo di accedere a ThatInstance.Bar si connetterà alla connessione "Bar" del pannello "Alpha" (che a sua volta è collegata a Alpha.Bar) .

Ci sono volte in cui lo shadowing è utile e appropriato, ma si deve fare molta attenzione quando si oscura un metodo o una proprietà di un oggetto che può essere passato al codice che si aspetta il tipo di base.

Problemi correlati