Ссылочное равенство типов значений
Я сделал несколько 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, поэтому набор значимых сценариев для управляемых ссылок будет крайне ограничен.