2012-02-07 8 views
8

Ho un'applicazione .NET che utilizza un assembly (DLL) che definisce un metodo:perché l'aggiunta di un tipo di ritorno a un metodo di ritorno vuoto provoca una MissingMethodException

public void DoSomething() 
    { 
     // Do work 
    } 

Supponiamo che questo metodo di firma cambia includendo un tipo stringa ritorno:

public string DoSomething() 
    { 
     // Do work 
     return "something"; 
    } 

Perché il codice che utilizza questo metodo non riesce in un System.MissingMethodException?

Mi sembra, che chiamano tutti i siti a questo metodo, non è stato fatto alcun uso del valore di ritorno (poiché non esisteva prima).

Perché questa modifica interrompe il codice?

+0

Modificato il mio esempio. Il mio punto era che il metodo restituisce qualcosa ora; ma tutto il codice che in precedenza lo utilizzava non faceva nulla con esso. È perfettamente legale scrivere questo codice. –

risposta

1

Perché hai fatto un cambio di rottura. Hai cambiato la firma del metodo in una classe base o in un'interfaccia.

I chiamanti sono collegati a questo metodo. In IL un riferimento al metodo non è solo un riferimento a un tipo e il suo metodo con qualche indice al metodo, ma i riferimenti al metodo chiamanti includono la firma del metodo completo.

Questa modifica è quindi risolvibile mediante una ricompilazione di tutti gli assembly che chiamano questo metodo, ma si ottengono eccezioni di run time quando si ricompila solo l'assembly modificato e si spera che gli assembly utilizzino magicamente la firma del metodo modificato. Questo non è il caso perché il riferimento al metodo contiene la firma del metodo completo e il tipo di definizione.

Anche a me è successo. Hai ragione che nessuno può utilizzare il tipo di reso in modo che questo cambiamento sia sicuro, ma è necessario ricompilare tutti i target interessati.

+0

Grazie. Avevo l'impressione che solo il nome del metodo + i tipi di parametro fossero usati per inferire quale metodo chiamare. –

+1

è in realtà typeref + methodref dove il riferimento al metodo contiene nome, tipo restituito, tipo arguemtns, argomenti generici e convenzione di chiamata –

+0

@liortal: Il tipo di ritorno non viene utilizzato da C# per determinare quale metodo chiamare. Ma C# ha sicuramente bisogno di conoscere il tipo di ritorno * per generare il codice corretto nel chiamante del metodo *. –

0

I produttori di C# hanno deciso che dovrebbe essere rigoroso sulle firme dei metodi.

+1

Dall'eccezione ('MissingMethodException') credo che descriva un errore di runtime causato dalla sostituzione della dll con un'altra. Se è così allora non ha niente a che fare con C#. –

2

Se non è presente alcuna riflessione ma il collegamento è statico (presumo questo dalla descrizione), il runtime tenterà di trovare un metodo utilizzando la firma esatta. È proprio come funziona lo callvirt di CIL.

Non importa se il valore è stato consumato o meno - il runtime non è in grado di individuare lo void YourClass::DoSomething() e non tenta nemmeno di cercare string YourClass::DoSomething().

Se tale modifica fosse possibile, si potrebbe facilmente far esplodere il tempo di esecuzione causando flussi/overflow dello stack.

+0

+1 Per non parlare del fatto che non vorrai indovinare e forse scegliere il metodo sbagliato - che potrebbe diventare fastidioso velocemente ... – Basic

+0

Ho pensato che il metodo scelto fosse solo guardando il suo nome + parametri. –

+2

Hai pensato male! – adelphus

1

Perché hai modificato la firma del metodo.

Quando il codice esterno deve individuare un metodo, è necessario assicurarsi che ciò che sta chiamando sia corretto. Memorizza alcune di queste informazioni come firma al momento della compilazione: le informazioni sulla firma includono il tipo di reso (indipendentemente dal fatto che sia effettivamente utilizzato ovunque).

Per quanto riguarda il CLR, un metodo con un tipo di reso nullo non esiste più, quindi lo MissingMethodException.

+0

Interessante. Mi chiedo che aspetto abbia la MSIL (prima e dopo). –

14

Le altre risposte che indicano che è stata modificata la firma di un metodo e che pertanto devono ricompilare i chiamanti, sono corrette.Ho pensato di aggiungere alcune informazioni aggiuntive su questo bit della tua domanda:

Mi sembra che a tutti i siti di chiamata a questo metodo non sia stato fatto alcun uso del valore di ritorno (poiché non esisteva prima).

Esattamente. Ora, considera questa domanda: come scrivi il codice che non fa uso dei dati? Sembra che tu stia lavorando sotto la falsa supposizione che non utilizzi un valore non richiede alcun codice, ma non usare un valore richiede sicuramente il codice!

Supponiamo di avere metodi:

static int M1(int y) { return y + 1; } 
static void M2(int z) { ... } 

e si dispone di una chiamata

int x; 
x = M1(123); 

Cosa succede a livello IL? il seguente:

  • Allocare lo spazio sul pool temporaneo per x.
  • Invia 123 in pila
  • Invoca M1.
  • Premere 1 sulla pila. Stack è ora 1, 123
  • Aggiungi le prime due cose in pila. Questo fa scattare entrambi e spinge il risultato. Stack è ora 124.
  • ritorno al chiamante
  • Lo stack è ancora 124.
  • Conservare il valore sullo stack nella memoria temporanea per x. Questo apre lo stack, quindi lo stack è vuoto.

Supponiamo ora fa:

M1(345); 

cosa succede? Stessa cosa:

  • Spingere 345 sullo stack
  • Invoke M1.
  • Premere 1 sulla pila. Stack è ora 1, 345
  • Aggiungi le prime due cose in pila. Questo fa scattare entrambi e spinge il risultato. Pila è ora 346.
  • ritorno al chiamante
  • Lo stack è ancora 346.

ma non c'è nessuna istruzione che memorizza il valore sullo stack da nessuna parte, quindi dobbiamo emettere un'istruzione pop:

  • Inserisci il valore inutilizzato fuori dallo stack.

Ora supponiamo che si chiama

M2(456); 

Che cosa succede?

  • Spingere 456 sullo stack
  • Invoke M2.
  • M2 fa il suo dovere. Quando ritorna al chiamante, lo stack è vuoto perché è vuoto restituire.
  • Ora lo stack è vuoto, quindi non farne uscire nulla.

Ora capisci perché la modifica di un metodo da ritorno a valore vuoto a valore restituito è un cambiamento di rottura? Ora ogni chiamante deve estrarre il valore inutilizzato dallo stack. Fare niente con dati richiede comunque di pulirlo dal pacco. Si sta disallineare lo stack se non si esclude tale valore; il CLR richiede che lo stack sia vuoto all'inizio di ogni istruzione per garantire che questo tipo di disallineamento non avvenga.

+1

+100 Achievement Unlocked - (Eric Lippert ha risposto alla tua domanda) –

+0

Uno stack bilanciato è una buona cosa. Ma a volte vorrei davvero copiare alcuni valori dalla pila del mio chiamante, in particolare l'handle del metodo per ottenere uno stack walk molto economico del mio chiamante. A livello IL non ho trovato alcun modo per farlo. Sei a conoscenza di tale possibilità o devo ricorrere a GetStackFramesInternal? –

+0

@AloisKraus lo stack di valutazione IL non è la stessa cosa dello stack di chiamate. Lo stack di valutazione è piuttosto più virtuale. Vedi la risposta di Eric http://stackoverflow.com/a/7877736/385844 per maggiori dettagli. – phoog