引用值类型的相等性

时间:2015-07-07 11:10:06

标签: c# ref boxing

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

4 个答案:

答案 0 :(得分:38)

  

为什么此代码会显示False

当您致电are being boxed时,int aint 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

  

比较值类型时。如果objAobjB值类型,   它们被装箱,然后传递给ReferenceEquals   方法。这意味着如果objAobjB都代表相同   值类型的实例,但ReferenceEquals方法   返回false

答案 3 :(得分:0)

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

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

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

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