int ref 参数是否被装箱?
Do int ref parameter 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);
}
输出为:
8 8 8
即 当堆栈展开时,ref 参数的值将保持不变。
这是否意味着将 ref keyword
添加到 int parameter
会导致它被装箱?
在递归调用期间实际堆栈看起来如何?
我认为您说 int
参数被装箱是错误的。来自 MSDN、
Boxing is the process of converting a value type to the type object or
to any interface type implemented by this value type
这里有一个 int
参数通过引用传递,具体来说它是一个 "value type" 通过引用传递。
具体可以参考Jon Skeet关于参数传递的精彩explanation
很容易创建一个程序,根据 ref int
是否被装箱给出不同的结果:
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);
}
你得到了什么?我看到打印了 3
。
装箱涉及创建副本,因此如果 ref a
被装箱,输出将是 5
。相反,a
和 b
都是对 Main
中原始 a
变量的引用。如果有帮助,您可以大部分(不完全)将它们视为指针。
你的Console.WriteLine(a);
将在递归完成后执行。当 int 的值变为 8 时,递归完成。为了使它达到 8,它递归了 3 次。因此,在 last 之后它将打印 8,然后将控制权传递给递归,递归将再次打印 8,因为引用的变量的值变为 8。
同时检查 ILDASM 输出
.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
不是拳击
MSDN中有明确的解释ref keyword documentation:
Do not confuse the concept of passing by reference with the concept of reference types. The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type. There is no boxing of a value type when it is passed by reference.
通过引用传递值类型会导致它在堆栈上的 位置 被传递而不是值本身。它与装箱和拆箱无关。这使得考虑递归调用期间堆栈的外观变得相当容易,因为每个调用都指向堆栈上的 "same" 位置。
我认为很多困惑来自MSDN's paragraph on boxing and unboxing:
Boxing is name given to the process whereby a value type is converted into a reference type. When you box a variable, you are creating a reference variable that points to a new copy on the heap. The reference variable is an object, ...
可能会使您混淆两种不同的事物:1) "converting",值类型 到说一个对象,根据定义,它是一个 引用类型:
int a = 5;
object b = a; // boxed into a reference type
和2)通过值类型参数的传递by reference:
main(){
int a = 5;
doWork(ref a);
}
void doWork(ref int a)
{
a++;
}
这是两个不同的东西。
在现有答案中添加这是如何实现的:
CLR 支持所谓的托管指针。 ref
将托管指针传递给堆栈上的变量。您还可以传递堆位置:
var array = new int[1];
F(ref array[0]);
您还可以传递对字段的引用。
这不会导致固定。运行时(特别是 GC)可以理解托管指针。它们是可重新定位的。它们是安全且可验证的。
假设我有以下代码:
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);
}
输出为:
8 8 8
即 当堆栈展开时,ref 参数的值将保持不变。
这是否意味着将 ref keyword
添加到 int parameter
会导致它被装箱?
在递归调用期间实际堆栈看起来如何?
我认为您说 int
参数被装箱是错误的。来自 MSDN、
Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type
这里有一个 int
参数通过引用传递,具体来说它是一个 "value type" 通过引用传递。
具体可以参考Jon Skeet关于参数传递的精彩explanation
很容易创建一个程序,根据 ref int
是否被装箱给出不同的结果:
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);
}
你得到了什么?我看到打印了 3
。
装箱涉及创建副本,因此如果 ref a
被装箱,输出将是 5
。相反,a
和 b
都是对 Main
中原始 a
变量的引用。如果有帮助,您可以大部分(不完全)将它们视为指针。
你的Console.WriteLine(a);
将在递归完成后执行。当 int 的值变为 8 时,递归完成。为了使它达到 8,它递归了 3 次。因此,在 last 之后它将打印 8,然后将控制权传递给递归,递归将再次打印 8,因为引用的变量的值变为 8。
同时检查 ILDASM 输出
.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
不是拳击
MSDN中有明确的解释ref keyword documentation:
Do not confuse the concept of passing by reference with the concept of reference types. The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type. There is no boxing of a value type when it is passed by reference.
通过引用传递值类型会导致它在堆栈上的 位置 被传递而不是值本身。它与装箱和拆箱无关。这使得考虑递归调用期间堆栈的外观变得相当容易,因为每个调用都指向堆栈上的 "same" 位置。
我认为很多困惑来自MSDN's paragraph on boxing and unboxing:
Boxing is name given to the process whereby a value type is converted into a reference type. When you box a variable, you are creating a reference variable that points to a new copy on the heap. The reference variable is an object, ...
可能会使您混淆两种不同的事物:1) "converting",值类型 到说一个对象,根据定义,它是一个 引用类型:
int a = 5;
object b = a; // boxed into a reference type
和2)通过值类型参数的传递by reference:
main(){
int a = 5;
doWork(ref a);
}
void doWork(ref int a)
{
a++;
}
这是两个不同的东西。
在现有答案中添加这是如何实现的:
CLR 支持所谓的托管指针。 ref
将托管指针传递给堆栈上的变量。您还可以传递堆位置:
var array = new int[1];
F(ref array[0]);
您还可以传递对字段的引用。
这不会导致固定。运行时(特别是 GC)可以理解托管指针。它们是可重新定位的。它们是安全且可验证的。