我做了一些ref
个关键字测试,有一个认为我无法理解:
static void Test(ref int a, ref int b)
{
Console.WriteLine(Int32.ReferenceEquals(a,b));
}
static void Main(string[] args)
{
int a = 4;
Test(ref a, ref a);
Console.ReadLine();
}
为什么此代码会显示False
?我知道int
是一个值类型,但在这里它应该传递对同一个对象的引用。
答案 0 :(得分:38)
为什么此代码会显示
False
?
当您致电are being boxed时,int a
和int b
object.ReferenceEquals
。每个整数都装在object
实例中。因此,您实际上是在比较两个盒装值之间的参考,这两个值显然不相等。
如果您查看方法的生成CIL,则可以轻松看到此内容:
Test:
IL_0000: nop
IL_0001: ldarg.0 Load argument a
IL_0002: ldind.i4
IL_0003: box System.Int32
IL_0008: ldarg.1 Load argument b
IL_0009: ldind.i4
IL_000A: box System.Int32
IL_000F: call System.Object.ReferenceEquals
IL_0014: call System.Console.WriteLine
IL_0019: nop
IL_001A: ret
可以通过使用可验证的CIL(例如@leppie's answer)或unsafe
代码来检查存储位置是否相等:
unsafe static void Main(string[] args)
{
int a = 4;
int b = 5;
Console.WriteLine(Test(ref a, ref a)); // True
Console.WriteLine(Test(ref a, ref b)); // False;
}
unsafe static bool Test(ref int a, ref int b)
{
fixed (int* refA = &a)
fixed (int* refB = &b)
{
return refA == refB;
}
}
答案 1 :(得分:18)
这不能直接在C#中完成。
然而,您可以在可验证的CIL中实现它:
.method public hidebysig static bool Test<T>(!!T& a, !!T& b) cil managed
{
.maxstack 8
ldarg.0
ldarg.1
ceq
ret
}
<强>测试强>
int a = 4, b = 4, c = 5;
int* aa = &a; // unsafe needed for this
object o = a, p = o;
Console.WriteLine(Test(ref a, ref a)); // True
Console.WriteLine(Test(ref o, ref o)); // True
Console.WriteLine(Test(ref o, ref p)); // False
Console.WriteLine(Test(ref a, ref b)); // False
Console.WriteLine(Test(ref a, ref c)); // False
Console.WriteLine(Test(ref a, ref *aa)); // True
// all of the above works for fields, parameters and locals
备注强>
这实际上并没有检查相同的参考,但更精细,因为它确保两者都是相同的位置&#39; (或从同一个变量引用)。这是第3行返回false
,即使o == p
返回true
。这个位置的有用性&#39;虽然测试非常有限。
答案 2 :(得分:2)
我知道,int是一个值类型,但在这里它应该传递对同一个对象的引用。
是的,传递给方法的引用是相同的,但它们在ReferenceEquals
方法中装箱(转换为对象/引用类型)。
这就是为什么测试结果返回false的原因,因为你要比较两个不同对象的引用,由于装箱。
请参阅:Object.ReferenceEquals Method
比较值类型时。如果
objA
和objB
是值类型, 它们被装箱,然后传递给ReferenceEquals
方法。这意味着如果objA
和objB
都代表相同 值类型的实例,但ReferenceEquals
方法 返回false
答案 3 :(得分:0)
这里的混淆是因为与指针(如*)不同,C#中的“ref”不是类型的一部分,而是方法签名的一部分。它适用于参数并表示“不得复制”。这并不意味着“这个论点有参考类型”。
ref传递的参数,而不是代表新的存储位置,而是某个现有位置的别名。如何创建别名在技术上是一个实现细节。大多数情况下,别名是作为托管引用实现的,但并非总是如此。例如,在一些异步相关的情况下,对数组元素的引用可以在内部表示为数组和索引的组合。
基本上,出于所有目的,您的a和b仍被C#理解为int类型变量。在任何采用类似a + b或SomeMethod(a,b)等int值的表达式中使用它们是合法且完全正常的,在这些情况下,使用存储在a和b中的实际int值。
实际上,没有“引用”概念作为您可以直接在C#中使用的实体。 与指针不同,必须假定托管引用的实际值能够随时或甚至异步地由GC更改,因此托管引用的有意义方案集将非常有限。