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。相反,ab 都是对 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)可以理解托管指针。它们是可重新定位的。它们是安全且可验证的。