简体   繁体   English

值类型的引用相等性

[英]Reference equality of value types

I have made some ref keyword tests and there is one thing I can't understand:我做了一些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();
}

Why does this code display False ?为什么此代码显示False I know that int is a value type but here it should pass references to the same object.我知道int是一种值类型,但在这里它应该传递对同一对象的引用。

Why does this code display False ?为什么此代码显示False

Because int a and int b are being boxed when you call object.ReferenceEquals .因为int aint b在您调用object.ReferenceEquals被装箱 Each integer is boxed inside an object instance.每个整数都装箱在一个object实例中。 Thus, you are actually comparing references between two boxed values, which clearly aren't equal.因此,您实际上是在比较两个明显不相等的盒装值之间的引用。

You can easily see this if you look at the generated CIL for the method:如果您查看为该方法生成的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

Checking for storage location equality can be achieved either by using verifiable CIL (such as in @leppie's answer ) or by unsafe code:可以通过使用可验证的 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;
    }
}

This cannot be done directly in C#.这不能直接在 C# 中完成。

You can however implement it in verifiable CIL:但是,您可以在可验证的 CIL 中实现它:

.method public hidebysig static bool Test<T>(!!T& a, !!T& b) cil managed
{
  .maxstack 8
  ldarg.0 
  ldarg.1 
  ceq 
  ret 
}

Tests测试

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

Notes笔记

This does not actually check for the same reference, but even more fine-grained in that it makes sure both are the same 'location' (or referenced from the same variable) too.这实际上并没有检查相同的引用,而是更细粒度的,因为它确保两者都是相同的“位置”(或从同一变量引用)。 This is while the 3rd line returns false even though o == p returns true .这是第三行返回false即使o == p返回true The usefulness of this 'location' test is very limited though.不过,这种“位置”测试的用处非常有限。

I know, that int is a value type but here it should pass references to the same object.我知道, int 是一种值类型,但在这里它应该传递对同一对象的引用。

Yes, the reference passed to the method are the same, but they are boxed (converted to object/reference type) in the ReferenceEquals method.是的,传递给方法的引用是相同的,但它们在ReferenceEquals方法中被装箱(转换为对象/引用类型)

That is why the result of your test returns false, since you are comparing references of two different objects, due to boxing .这就是为什么您的测试结果返回 false 的原因,因为您正在比较两个不同对象的引用,由于 boxing

See: Object.ReferenceEquals Method请参阅: Object.ReferenceEquals 方法

When comparing value types.比较值类型时。 If objA and objB are value types, they are boxed before they are passed to the ReferenceEquals method.如果objAobjB值类型,则在将它们传递给ReferenceEquals方法之前将它们装箱 This means that if both objA and objB represent the same instance of a value type, the ReferenceEquals method nevertheless returns false这意味着如果objAobjB表示值类型的相同实例ReferenceEquals方法仍然返回 false

The confusion here is because unlike pointers (as in * ), "ref" in C# is not a part of a type, but a part of a method signature.这里的混淆是因为与指针(如 * )不同,C# 中的“ref”不是类型的一部分,而是方法签名的一部分。 It applies to the parameter and means "this must not be copied".它适用于参数,表示“不得复制”。 It does not mean "this argument has reference type".这并不意味着“此参数具有引用类型”。

Parameter passed by ref, instead of representing a new storage location, is instead an alias to some existing location. ref 传递的参数不是表示新的存储位置,而是某个现有位置的别名。 How alias is created is technically an implementation detail.如何创建别名在技术上是一个实现细节。 Most often aliases are implemented as managed references, but not always.大多数情况下,别名实现为托管引用,但并非总是如此。 In some async related cases, for example, a reference to an array element could be internally represented as a combination of array and index.例如,在一些与异步相关的情况下,对数组元素的引用可以在内部表示为数组和索引的组合。

Essentially for all purposes your a and b are still understood by C# as int-typed variables.基本上出于所有目的,你的 a 和 b 仍然被 C# 理解为 int 类型的变量。 It is legal and completely normal to use them in any expression that takes int values like a+b, or SomeMethod(a,b) and in those cases the actual int values stored in a and b are used.在任何采用像 a+b 或 SomeMethod(a,b) 这样的 int 值的表达式中使用它们是合法且完全正常的,在这些情况下,使用存储在 a 和 b 中的实际 int 值。

There is really no concept of a "reference" as an entity that you can directly work with in C#.实际上没有“引用”的概念作为可以在 C# 中直接使用的实体。 Unlike pointers, the actual values of managed references must be assumed to be able to change at any moment, or even asynchronously, by GC, so the set of meaningful scenarios on managed references would be extremely limited.与指针不同,托管引用的实际值必须被假定为能够随时更改,甚至可以通过 GC 异步更改,因此托管引用的一组有意义的场景将非常有限。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM