2016-07-30 9 views
4

Für den folgenden Code-Schnipsel:Warum Int32.ToString() Call-Anweisung statt Callvirt ausgeben?

struct Test 
{ 
    public override string ToString() 
    { 
     return ""; 
    } 
} 

public class Program 
{ 
    public static void Main() 
    { 
     Test a = new Test(); 
     a.ToString(); 
     Int32 b = 5; 
     b.ToString(); 
    } 
} 

Compiler die folgenden IL aussendet:

.locals init ([0] valuetype ConsoleApplication2.Test a, 
      [1] int32 b) 
    IL_0000: nop 
    IL_0001: ldloca.s a 
    IL_0003: initobj ConsoleApplication2.Test 
    IL_0009: ldloca.s a 
    IL_000b: constrained. ConsoleApplication2.Test 
    IL_0011: callvirt instance string [mscorlib]System.Object::ToString() 
    IL_0016: pop 
    IL_0017: ldc.i4.5 
    IL_0018: stloc.1 
    IL_0019: ldloca.s b 
    IL_001b: call  instance string [mscorlib]System.Int32::ToString() 
    IL_0020: pop 
    IL_0021: ret 

Da beide Typwert Test und Int32 die ToString() Methode überschreiben, ich glaube, kein Boxen in beide a.ToString() auftreten und b.ToString(). Daher frage ich mich, warum der Compiler constraned + callvirt für Test und call für Int32 ausstrahlt?

Antwort

6

Dies ist eine Optimierung durch den Compiler für primitive Typen getan ist.

Aber selbst für benutzerdefinierte Strukturen, callvirt wird tatsächlich als call zur Laufzeit aufgrund der constrained. Opcode ausgeführt - in dem Fall, wo die Methode überschrieben wurde. Dadurch kann der Compiler in beiden Fällen die gleichen Anweisungen ausgeben und von der Laufzeit verarbeiten lassen.

Von MSDN:

thisType Wenn ein Werttyp ist und thisType implementiert method dann ptr unmodifizierten als this Zeiger übergeben wird, auf eine call Verfahren Anweisung, für die Durchführung des Verfahrens nach thisType.

Und:

Der constrained Opcode ermöglicht IL-Compiler in einer einheitlichen Art und Weise unabhängig von einen Aufruf einer virtuellen Funktion zu machen, ob ptr ein Werttyp oder ein Referenztyp. Obwohl es für den Fall vorgesehen ist, dass thisType eine generische Typvariable ist, funktioniert das eingeschränkte Präfix auch für nicht generische Typen und kann die Komplexität beim Generieren virtueller Aufrufe in Sprachen reduzieren, die die Unterscheidung zwischen Werttypen und Referenztypen verbergen.

Ich kenne keine offizielle Dokumentation für die Optimierung, aber Sie können die Bemerkungen im Roslyn Repo für die MayUseCallForStructMethod method sehen.

Warum diese Optimierung auf die Laufzeit für nicht-primitive Typen verschoben wird, liegt meiner Meinung nach daran, dass sich die Implementierung ändern kann. Stellen Sie sich vor, eine Bibliothek referenziert zu haben, die ursprünglich eine Überschreibung für ToString hatte, und dann die DLL (ohne Neukompilierung!) In eine zu ändern, in der die Überschreibung entfernt wird. Dies hätte eine Laufzeitausnahme verursacht. Für Primitive können sie sicher sein, dass es nicht passieren wird.

+0

Vielen Dank, ich denke auch, es ist die Compiler-Optimierung funktioniert. Aber ich kann kein Material finden, das meine Vermutung stützt. Ich würde es also schätzen, wenn Sie irgendwelche Dokumente über diese spezielle Optimierung für primitive Typen bereitstellen könnten. Danke noch einmal. –

+0

@LifuHuang Meine Antwort aktualisiert. –

+0

Danke @Eli Arbel, du hast meine Frage wirklich gelöst:) –

0

Es ist, weil Int ein Framework zur Verfügung gestellt abgedichteten Typ ist, und es wird nie passieren, dass einige andere Art überschreibt ToString Methode int, so Compiler weiß, dass es immer die ToString() Methode Umsetzung im int Art vorzulegenden zu nennen, so tut es Verwenden Sie nicht callvirt, um herauszufinden, welche Implementierung aufgerufen werden soll.

Für primitve Typen weiß Compiler, welche Implementierung von ToString aufgerufen werden soll, aber wenn wir einen benutzerdefinierten Werttyp erstellen, ist es ein neuer, den es vorher noch nie gab, also weiß der Compiler nichts darüber und er muss es verstehen out über die Implementierung, die man aufrufen und wo es residiert, wie er standardmäßig von Object erbt, so muss der Compiler callvirt tun, um die ToString() Implementierung für benutzerdefinierten Typ zur Verfügung gestellt, wenn nicht überschrieben wird es den Objekttyp aufrufen, die offensichtlich ist.

folgenden bestehenden SO können Beiträge Ihnen helfen, dies zu verstehen:

Call and Callvirt

+1

Aber alle Werttypen sind implizit versiegelt, daher denke ich, dass sich "Int32" und "Test" in dieser Hinsicht genauso verhalten sollten. Bitte korrigieren Sie mich, wenn ich falsch liege. –

+0

ja es ist ein Rahmen zur Verfügung gestellt versiegelt Typ, das ist Sonderfall –

+0

Sorry, ich weiß nicht, was Ihr "Sonderfall" hier bedeutet. Würden Sie mir bitte mehr Details geben? –

Verwandte Themen