Подтвердить что ты не робот

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

Я сделал несколько тестов 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 - тип значения, но здесь он должен передавать ссылки на один и тот же объект.

4b9b3361

Ответ 1

Почему этот код показывает 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 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;
    }
}

Ответ 2

Это невозможно сделать непосредственно в С#.

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

Примечания

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

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