ReferenceEquals возвращает false для переменных, ссылающихся на один и тот же экземпляр структуры

Вот мой код, с помощью которого я пытаюсь вызвать метод object.ReferenceEquals для двух переменных, ссылающихся на один и тот же экземпляр структуры:

static void Main(string[] args)
{
    var myref = new Group();
    var myref2 = myref;
    if (object.ReferenceEquals(myref, myref2))
    {
        Console.WriteLine("The references are equal.");
    }
    else
    {
        Console.WriteLine("The references are NOT equal.");
    }
}

Структура Group была определена следующим образом:

struct Group
{
    public int StudentCount { get; set; }
}

Интересно, что он печатает сообщение в блоке else, которое предполагает, что myref и myref2 указывают на разные объекты:

Ссылки НЕ равны.

Как это возможно? Я создал экземпляр структуры Group только один раз в начале функции Main.


person RBT    schedule 16.06.2018    source источник
comment
Структуры являются типами значений. Вы также можете попытаться создать две ссылки на один и тот же int. Вы не можете; вместо этого вы создадите две копии.   -  person John Wu    schedule 16.06.2018
comment
Обратите внимание, что object.ReferenceEquals принимает два объекта. Структуры будут упакованы в два разных объекта в куче и, следовательно, не будут равны по ссылкам.   -  person Mike Zboray    schedule 16.06.2018
comment
@mikez ага. Это имеет смысл. Так что на самом деле это вызов метода object.ReferenceEquals, который вызывает всю магию. Таким образом, до момента, когда метод ReferenceEquals вызывается в операторе if, myref и myref2 указывали на одно и то же место в памяти. Правильный?   -  person RBT    schedule 16.06.2018
comment
@RBT Нет, это не одно и то же место в памяти, это разные переменные. Я имею в виду, что даже если вы сравниваете одну и ту же структурную переменную с самой собой, она не равна ссылке из-за бокса.   -  person Mike Zboray    schedule 16.06.2018


Ответы (2)


Структуры передаются по значению, а не по ссылке. У вас есть два экземпляра структуры, потому что при назначении значения копируются, а не ссылаются.

person cwharris    schedule 16.06.2018
comment
Итак, просто по заданию var myref2 = myref; я создаю два новых экземпляра Group в памяти. Это? - person RBT; 16.06.2018
comment
Так работают структуры, да. Они размещаются в стеке точно так же, как целые числа, числа с плавающей запятой, удвоения и т. д., и поэтому освобождаются при выходе из области видимости, в отличие от экземпляра класса, который размещается в куче, а чья ссылка освобождается при выходе. область действия, но чье значение нужно будет собирать из кучи мусора, когда больше не существует ссылок. - person cwharris; 16.06.2018
comment
Это основное различие между классами и структурами — распределение кучи и стека соответственно. - person cwharris; 16.06.2018
comment
Исправление... вы выделяете не два новых экземпляра, вы выделяете один новый экземпляр. myref копируется в myref2, но myref по-прежнему использует ту же память, что и раньше. И технически это не экземпляры... это просто память, выделенная в стеке. - person cwharris; 16.06.2018
comment
Я бы немного отличался от - That is the primary difference between classes and structs - heap vs stack allocation, respectively. : это не верно в целом, как указано здесь. - person RBT; 16.06.2018
comment
Справедливо. Я действительно говорил с точки зрения реализации. - person cwharris; 16.06.2018

Я провел еще несколько исследований и, наконец, смог найти что-то конкретное на основе комментария Майка З. Воспроизведение соответствующих частей этот блог, в котором рассказывается об очень важном поведении ReferenceEquals в случае structs, которые являются типами значений. Здесь вместо структуры он взял целочисленное значение, которое также является типом значения. Результат остается прежним.

TL;DR; Object.ReferenceEquals(valueVar, valueVar) всегда возвращает false для структуры, будь то одна и та же переменная или разные переменные.

Подробности. Метод ReferenceEquals обычно используется для определения того, являются ли два объекта одним и тем же экземпляром. Но вам нужно быть немного осторожным, когда вы используете его с типами значений. Рассмотрим следующий код:

static void Main(string[] args)
{
    int valueVar = 15;

    if (Object.ReferenceEquals(valueVar, valueVar))
        Console.WriteLine("Reference Equal");
    else
        Console.WriteLine("Reference Not Equal");

    Console.ReadLine();
} // Will always print "Reference Not Equal"

Этот код всегда будет печатать «Reference Not Equal», если valueVar является переменной типа значения (которая также включает структуру). Если вы исследуете Object.ReferenceEquals, он предназначен для использования двух объектов в качестве входных параметров. Поэтому, когда вы передаете ему типы значений, .NET идет дальше и «упаковывает» параметры. Вот соответствующий MSIL, созданный для приведенного выше кода.

введите здесь описание изображения

Это «боксирование» приводит к созданию двух разных объектов в куче, которые теперь будут использоваться для сравнения. Вы можете использовать SOS, чтобы убедиться в этом.

введите здесь описание изображения

Таким образом, мы в конечном итоге делаем ReferenceEquals между объектами по адресам 0x01d917e0 (адрес одного объекта) и 0x01d917ec (адрес другого объекта), что, очевидно, не удастся (помните, что теперь это объекты ссылочного типа, поэтому адреса сравниваются) и вернет false.

В заключение, несмотря на то, что Object.ReferenceEquals(valueVar, valueVar) передает ту же переменную, что и оба параметра, он всегда будет возвращать false.

person RBT    schedule 16.06.2018
comment
Простите мою наивность здесь, но кажется, что ответ ниже более прост и прямолинеен. Структуры — это типы значений, а типы значений просто ни на что не ссылаются — они просто хранят значения в памяти. Итак, как я понимаю, использование ReferenceEquals с типами значений всегда будет возвращать false. Не уверен, почему он не выдает исключение, но, возможно, это просто устаревшее решение на уровне CLR. - person Christian Findlay; 23.05.2020