引用值类型的相等性

我做了一些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是一个值类型,但在这里它应该传递对同一个对象的引用。

为什么此代码显示为False

因为当调用object.ReferenceEquals时, int aint b 被装箱 。 每个整数都装在一个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 

通过使用可validation的CIL(例如@ leppie的答案 )或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; } } 

这不能直接在C#中完成。

但是,您可以在可validation的CIL中实现它:

 .method public hidebysig static bool Test(!!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 

笔记

这实际上并没有检查相同的引用,但更精细,因为它确保两者都是相同的“位置”(或从同一个变量引用)。 这是第3行返回false即使o == p返回true 。 然而,这种“位置”测试的有用性非常有限。

我知道,int是一个值类型,但在这里它应该传递对同一个对象的引用。

是的,传递给方法的引用是相同的,但它们在ReferenceEquals方法中被加框(转换为对象/引用类型)

这就是为什么测试结果返回false的原因,因为你正在比较两个不同对象的引用, 因为装箱

请参阅: Object.ReferenceEquals方法

比较值类型时。 如果objAobjB值类型,则在将它们传递给ReferenceEquals方法之前将它们装箱 。 这意味着如果objAobjB表示值类型的相同实例 ,则ReferenceEquals方法仍然返回false

这里的混淆是因为与指针(如*)不同,C#中的“ref”不是类型的一部分,而是方法签名的一部分。 它适用于参数,表示“不得复制”。 它并不意味着“此参数具有引用类型”。

ref传递的参数不是代表新的存储位置,而是某个现有位置的别名。 如何创建别名在技术上是一个实现细节。 大多数情况下,别名是作为托管引用实现的,但并非总是如此。 例如,在一些异步相关的情况下,对数组元素的引用可以在内部表示为数组和索引的组合。

基本上对于所有目的,您的a和b仍被C#理解为int类型变量。 在任何采用类似a + b或SomeMethod(a,b)等int值的表达式中使用它们是合法且完全正常的,在这些情况下,使用存储在a和b中的实际int值。

实际上没有“引用”的概念作为您可以直接在C#中使用的实体。 与指针不同,必须假定托管引用的实际值能够随时或甚至异步地由GC更改,因此托管引用的有意义方案集将非常有限。