Warum das Hinzufügen eines Rückgabetyps zu einer void-Rückgabe-Methode eine MissingMethodException verursacht

8

Ich habe eine .NET-Anwendung, die eine Assembly (.dll) verwendet, die eine Methode definiert:

%Vor%

Angenommen, diese Methodensignatur ändert sich und enthält einen String Rückgabetyp:

%Vor%

Warum schlägt der Code, der diese Methode verwendet, für eine System.MissingMethodException fehl?

Es scheint mir, dass an allen Aufrufstellen dieser Methode der Rückgabewert nicht verwendet wurde (da er vorher nicht existierte).

Warum bricht diese Änderung den Code dann?

    
lysergic-acid 07.02.2012, 15:02
quelle

5 Antworten

1

Weil du einen api-Wechsel gemacht hast. Sie haben entweder die Methodensignatur in einer Basisklasse oder in einer Schnittstelle geändert.

Die Anrufer sind mit dieser Methode verbunden. In IL ist eine Methodenreferenz nicht nur eine Referenz auf einen Typ und seine Methode mit einem Index für die Methode, sondern die Referenzen der Aufrufermethoden enthalten auch die vollständige Methodensignatur.

Diese Änderung ist daher durch eine Neukompilierung aller Assemblys, die diese Methode aufrufen, reparierbar, aber Sie erhalten Laufzeitausnahmen, wenn Sie nur die geänderte Assembly neu kompilieren und hoffen, dass die verwendenden Assemblys die geänderte Methodensignatur magisch übernehmen. Dies ist nicht der Fall, da die Methodenreferenz die vollständige Methodensignatur und den Definitionstyp enthält.

Es ist mir auch passiert. Sie haben Recht, dass niemand den Rückgabetyp verwenden kann, daher ist diese Änderung sicher, aber Sie müssen alle betroffenen Ziele neu kompilieren.

    
Alois Kraus 07.02.2012, 15:09
quelle
14

Die anderen Antworten, die angeben, dass Sie die Signatur einer Methode geändert haben und daher die Aufrufer neu kompilieren müssen, sind korrekt. Ich dachte, ich könnte einige zusätzliche Informationen über dieses Bit Ihrer Frage hinzufügen:

  

Es scheint mir, dass an allen Aufrufstellen dieser Methode der Rückgabewert nicht verwendet wurde (da er vorher nicht existierte).

Das stimmt genau. Betrachten Sie nun diese Frage: Wie schreibt man Code, der keine Daten verwendet ? Sie scheinen unter der völlig falschen Annahme zu arbeiten, dass keinen Wert verwenden keinen Code erfordert, aber keinen Wert zu verwenden, erfordert Code!

Angenommen, Sie haben Methoden:

%Vor%

und Sie haben einen Anruf

%Vor%

Was passiert auf der IL-Ebene ? das Folgende:

  • Vergeben Sie Speicherplatz im temporären Pool für x.
  • Drücken Sie 123 auf dem Stapel
  • Rufen Sie M1 auf.
  • Drücke 1 auf dem Stapel. Stack ist jetzt 1, 123
  • Fügen Sie die zwei wichtigsten Dinge auf dem Stapel hinzu. Dies öffnet beide und drückt das Ergebnis. Stack ist jetzt 124.
  • Kehre zum Anrufer zurück
  • Der Stapel ist immer noch 124.
  • Speichern Sie den Wert auf dem Stapel im temporären Speicher für x. Dadurch wird der Stapel geöffnet, sodass der Stapel jetzt leer ist.

Angenommen, Sie tun jetzt:

%Vor%

Was passiert? Gleiches :

  • Drücken Sie 345 auf den Stapel
  • Rufen Sie M1 auf.
  • Drücke 1 auf dem Stapel. Stack ist jetzt 1, 345
  • Fügen Sie die zwei wichtigsten Dinge auf dem Stapel hinzu. Dies öffnet beide und drückt das Ergebnis. Stack ist jetzt 346.
  • Kehre zum Anrufer zurück
  • Der Stapel ist immer noch 346.

Aber es gibt keine Anweisung, die den Wert irgendwo auf dem Stack speichert, also müssen wir eine Pop-Anweisung ausgeben:

  • Bringe den unbenutzten Wert aus dem Stapel.

Angenommen, Sie rufen

an %Vor%

Was passiert?

  • Drücken Sie 456 auf dem Stapel
  • Rufen Sie M2 auf.
  • M2 macht seine Sache. Wenn es zum Aufrufer zurückkehrt, ist der Stapel leer, da keine Rückgabe möglich ist.
  • Der Stapel ist jetzt leer, also lasst nichts davon los.

Siehst du jetzt, warum eine Änderung der Methode von void returning auf value return eine brechende Änderung ist? Jeder Aufrufer muss jetzt den nicht verwendeten Wert vom Stapel entfernen . Wenn Sie nothing mit Daten ausführen, müssen Sie es weiterhin vom Stapel löschen . Sie verschieben den Stapel falsch, wenn Sie diesen Wert nicht deaktivieren. Die CLR erfordert, dass der Stapel zu Beginn jeder Anweisung leer ist, um sicherzustellen, dass diese Art von Fehlausrichtung nicht auftritt.

    
Eric Lippert 07.02.2012 15:55
quelle
2

Wenn es keine Reflexion gibt, aber die Verknüpfung statisch ist (ich nehme das aus der Beschreibung an), wird die Laufzeit versuchen, eine Methode zu finden, die die exakte Signatur verwendet. Es funktioniert nur wie% callvirt von CIL funktioniert.

Es ist dann egal, ob der Wert verbraucht wird oder nicht - die Laufzeitumgebung kann void YourClass::DoSomething() nicht finden und versucht nicht einmal nach string YourClass::DoSomething() zu suchen.

Wenn eine solche Änderung möglich war, könnten Sie die Laufzeit leicht sprengen, indem Sie Stack-Unterläufe / Überläufe verursachen.

    
Wiktor Zychla 07.02.2012 15:08
quelle
1

Weil Sie die Methodensignatur geändert haben.

Wenn der externe Code eine Methode finden muss, muss er sicherstellen, dass der Aufruf korrekt ist. Es speichert einige dieser Informationen als eine Signatur zum Zeitpunkt der Kompilierung - die Signaturinformationen enthalten den Rückgabetyp (unabhängig davon, ob er tatsächlich irgendwo verwendet wird).

Was die CLR betrifft, gibt es keine Methode mit einem void return type - daher die MissingMethodException .

    
adelphus 07.02.2012 15:08
quelle
0

Die Macher von C # beschlossen, dass es streng nach Methodensignaturen sein sollte.

    
John Pick 07.02.2012 15:08
quelle

Tags und Links