[英]HashSet<int>.Add(int) doesn't accept the parameter of boxed int
[英]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
会导致它被装箱?
在递归调用期间,实际堆栈如何?
通过引用传递值类型会导致其在堆栈上的位置传递而不是值本身。 它与装箱和拆箱无关。 这使得在递归调用期间堆栈的外观变得相当容易,因为每个调用都指向堆栈上的“相同”位置。
我认为很多混淆来自MSDN关于拳击和拆箱的段落 :
Boxing是给定进程的名称,其中值类型被转换为引用类型。 当您装入变量时,您将创建一个指向堆上的新副本的引用变量。 引用变量是一个对象,...
您可能会在两个不同的事物之间混淆: 1)按照您的意愿“转换”一个值类型来表示一个对象,根据定义它是一个引用类型 :
int a = 5;
object b = a; // boxed into a reference type
2) 通过引用传递值类型参数:
main(){
int a = 5;
doWork(ref a);
}
void doWork(ref int a)
{
a++;
}
这是两件不同的事情。
创建一个可以提供不同结果的程序很容易,具体取决于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
是原来的引用a
变量Main
。 如果它有帮助,你可以大多数(不完全)将它们视为指针。
添加现有答案如何实现:
CLR支持所谓的托管指针。 ref
将托管指针传递给堆栈上的变量。 您还可以传递堆位置:
var array = new int[1];
F(ref array[0]);
您还可以将引用传递给字段。
这不会导致钉扎。 运行时(尤其是GC)可以理解托管指针。 它们是可重新定位的。 它们是安全可靠的。
你的Console.WriteLine(a);
将在递归完成后执行。 当int的值变为8时,递归完成。为了使其为8,它递归3次。 因此,在最后它将打印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关键字文档中有明确的解释:
不要将引用传递的概念与引用类型的概念混淆。 这两个概念不尽相同。 无论是值类型还是引用类型,都可以通过ref修改方法参数。 通过引用传递时,没有值类型的装箱。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.