2015-01-07 7 views
19

Say I haben den folgenden Code:Wird der Parameter int ref get boxed?

void Main() 
{ 
    int a = 5; 
    f1(ref a); 
} 

public void f1(ref int a) 
{ 
    if(a > 7) return; 
    a++; 
    f1(ref a); 
    Console.WriteLine(a); 
} 

Ausgang ist:

8 8 8 

d.h., wenn der Stapel um den Wert des Parameters abwickelt ref gehalten wird.

Bedeutet dies, dass das Hinzufügen von ref keyword zu int parameter dazu führt, dass es gerahmt wird?
Wie sieht der tatsächliche Stack während des rekursiven Aufrufs aus?

+11

Möglicherweise mein Mangel an Wissen, aber lassen Sie uns gehen: Übergabe eines Werttyps durch Ref bewirkt, dass seine Position auf dem Stapel übergeben wird und nicht der Wert selbst. Es hat nichts mit Boxen und Unboxing zu tun, richtig? – Adimeus

+5

Von MSDN: "Es gibt kein Boxen eines Werttyps, wenn es als Verweis übergeben wird." –

Antwort

24

Wert Typ Übergabe als Referenz bewirkt, dass seine Position auf dem Stack übergeben zu lassen, anstatt den Wert selbst. Es hat nichts mit Boxen und unboxing zu tun. Dies macht es ziemlich einfach, darüber nachzudenken, wie der Stapel während der rekursiven Aufrufe aussieht, da sich jeder einzelne Aufruf auf die "gleiche" Position auf dem Stapel bezieht.

Ich denke, eine Menge Verwirrung aus MSDN's paragraph on boxing and unboxing kommt:

Boxing Namen für den Prozess gegeben ist, wodurch ein Werttyp in einen Referenztyp umgewandelt wird. Wenn Sie eine Variable boxieren, erstellen Sie eine Referenzvariable, die auf eine neue Kopie im Heap verweist. Die Bezugsgröße ist ein Objekt, ...

Möglicherweise Sie zwischen zwei verschiedenen Dingen verwirrend: 1) der „Umwandlung“, wie man will, einen Werttyp ein Objekt zu sagen, die durch ist Definition ein Referenztyp:

int a = 5; 
object b = a; // boxed into a reference type 

und 2) mit dem Passieren eines Werttyp Parameter durch Referenz:

main(){ 
    int a = 5; 
    doWork(ref a); 
} 
void doWork(ref int a) 
{ 
    a++; 
} 

Was sind zwei verschiedene Dinge.

+3

Wahrscheinlich die beste Erklärung ... Beachten Sie, dass "auf dem Stapel" in der Frage der spezielle Fall erwähnt wird, Werttyp muss nicht auf dem Stapel zugeordnet werden. Vielleicht kann die Aussage "absolute Position (d. H. Auf Stapel)" generischer sein - nicht sicher, wie es für Leute ohne C/C++ - Hintergrund gelesen wird. –

2

Ich glaube, Sie irren sich, wenn Sie sagen, dass der int Parameter eingerahmt wird. Von MSDN,

Boxen ist der Prozess, einen Werttyp mit dem Typ Objekt oder einem Interface-Typ durch diesen Wert Typ

Was Sie hier implementiert der Umwandlung haben, ist ein int Parameter durch geführt wird Referenz, speziell ist es ein "Werttyp", der als Referenz übergeben wird.

Sie können Jon Skeet ausgezeichnete explanation auf Parameter-Passing für Details verweisen.

+0

Ja, aber zur Laufzeit ist der Parametertyp von 'f1' nicht' System.Int32', sondern der sogenannte ref-Typ 'System.Int32 &', der sich selbst nicht als Werttyp meldet. –

8

Es ist einfach, ein Programm zu erstellen, die unterschiedliche Ergebnisse je nach geben könnte, ob ref int eingerahmt wird:

static void Main() 
{ 
    int a = 5; 
    f(ref a, ref a); 
} 

static void f(ref int a, ref int b) 
{ 
    a = 3; 
    Console.WriteLine(b); 
} 

Was bekommen Sie? Ich sehe 3 gedruckt.

Boxing beinhaltet das Erstellen von Kopien, also wenn ref a Boxed wäre, wäre die Ausgabe 5 gewesen. Stattdessen sind sowohl a als auch b Verweise auf die ursprüngliche a Variable in Main. Wenn es hilft, kann man sie meistens (nicht ganz) als Zeiger betrachten.

3

Ihre Console.WriteLine(a); wird ausgeführt, nachdem die Rekursion beendet ist. Rekursion ist beendet, wenn der Wert von int zu 8 wird. Und um es zu 8 zu machen, hat es Rekursion für 3 mal. Also, nach dem letzten wird es 8 drucken und dann die Steuerung an Rekursion passieren, über dem 8 gedruckt wird wieder als Variablenwert bezeichnet hat wurde 8.

Auch ILDASM Ausgang

.method public hidebysig static void f1(int32& a) cil managed 
{ 
    // Code size  26 (0x1a) 
    .maxstack 8 
    IL_0000: ldarg.0 
    IL_0001: ldind.i4 
    IL_0002: ldc.i4.7 
    IL_0003: ble.s  IL_0006 
    IL_0005: ret 
    IL_0006: ldarg.0 
    **IL_0007: dup** 
    IL_0008: ldind.i4 
    IL_0009: ldc.i4.1 
    IL_000a: add 
    IL_000b: stind.i4 
    IL_000c: ldarg.0 
    IL_000d: call  void ConsoleApplication1.Program::f1(int32&) 
    IL_0012: ldarg.0 
    IL_0013: ldind.i4 
    IL_0014: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_0019: ret 
} // end of method Program::f1 
+1

Das ist alles in Ordnung, aber es beantwortet keine von Pawans Fragen. :) – gehho

+0

Soweit ich weiß, ist Boxen nicht passiert. Es drückt nur den neuesten Wert auf dem Stapel. – Amit

2

prüfen Es ist kein Boxen.

Es gibt klare Erklärung in MSDN ref keyword documentation:

Sie das Konzept der Weitergabe durch Bezugnahme mit dem Konzept der Referenztypen nicht verwirren. Die beiden Konzepte sind nicht gleich. Ein Methodenparameter kann mit ref geändert werden, unabhängig davon, ob es sich um einen Werttyp oder einen Referenztyp handelt. Es gibt kein Boxen eines Werttyps, wenn es als Referenz übergeben wird.

2

Zusätzlich zu den vorhandenen Antworten wie dies umgesetzt wird:

Die CLR unterstützt so verwaltet Zeiger genannt. ref übergibt einen verwalteten Zeiger an die Variable im Stapel. Sie können auch Heap-Speicherorte übergeben:

var array = new int[1]; 
F(ref array[0]); 

Sie können auch Verweise auf Felder übergeben.

Dies führt nicht zu Pinning. Verwaltete Zeiger werden von der Laufzeit (insbesondere vom GC) verstanden. Sie sind verlagerbar. Sie sind sicher und verifizierbar.