Ссылочное равенство типов значений

Я сделал несколько 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 ответа

Почему этот код отображается False?

Так как 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), либо с помощью 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#.

Однако вы можете реализовать его в проверяемом 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

Заметки

Это на самом деле не проверяет одну и ту же ссылку, но даже более детально, так как гарантирует, что оба объекта имеют одинаковое "местоположение" (или ссылаются из одной и той же переменной). Это пока третья строка возвращается false даже если o == p возвращается true, Однако полезность этого теста "местоположение" очень ограничена.

Я знаю, что int является типом значения, но здесь он должен передавать ссылки на один и тот же объект.

Да, ссылка, передаваемая методу, одинакова, но они заключены в квадрат (преобразован в тип объекта / ссылки) в ReferenceEquals метод.

Вот почему результат вашего теста возвращает false, так как вы сравниваете ссылки двух разных объектов из-за бокса.

Смотрите: метод Object.ReferenceEquals

При сравнении типов значений. Если objA а также objB являются типами значений, они упаковываются перед передачей в ReferenceEquals метод. Это означает, что если оба objA а также objB представляют один и тот же экземпляр типа значения, ReferenceEquals метод, тем не менее, возвращает false

Путаница здесь заключается в том, что в отличие от указателей (как в *), "ref" в C# является не частью типа, а частью сигнатуры метода. Это относится к параметру и означает "это не должно копироваться". Это не означает, что "этот аргумент имеет ссылочный тип".

Параметр, передаваемый ref вместо представления нового места хранения, является псевдонимом некоторого существующего места. То, как создается псевдоним, технически является деталью реализации. Чаще всего псевдонимы реализуются как управляемые ссылки, но не всегда. Например, в некоторых случаях, связанных с асинхронностью, ссылка на элемент массива может быть внутренне представлена ​​как комбинация массива и индекса.

По сути, для всех целей ваши a и b все еще понимаются в C# как переменные типа int. Допустимо и совершенно нормально использовать их в любом выражении, которое принимает значения типа int, например, a+b или SomeMethod(a,b), и в этих случаях используются фактические значения типа int, сохраненные в a и b.

На самом деле нет понятия "ссылка" как сущности, с которой вы можете напрямую работать в C#. В отличие от указателей, предполагается, что фактические значения управляемых ссылок могут изменяться в любой момент или даже асинхронно GC, поэтому набор значимых сценариев для управляемых ссылок будет крайне ограничен.

Другие вопросы по тегам