# V3161. Comparing value type variables with 'ReferenceEquals' is incorrect because compared values will be boxed.

Анализатор обнаружил подозрительный вызов метода 'Object.ReferenceEquals': один или оба переданных аргумента могут иметь значимый тип. Так как метод принимает параметры типа 'Object', при передаче значимых типов будет произведена упаковка. Таким образом, в 'Object.ReferenceEquals' будет передана ссылка на объект, созданный в куче при упаковке. Так как полученная ссылка не будет равна никакой другой, из этого следует, что метод будет возвращать 'false' независимо от значения переданного аргумента.

Рассмотрим простой синтетический пример:

    void SyntheticMethod(Point x, object a)
    {
      if (Object.ReferenceEquals(x, a))
        ....
    }

В качестве аргументов в 'Object.ReferenceEquals' передаются 'x' и 'a'. Переменная 'x' имеет тип 'Point', следовательно, её значение будет упаковано при преобразовании в 'Object'. Таким образом, вызов 'Object.ReferenceEquals' всегда будет возвращать 'false' вне зависимости от того, какие аргументы переданы в 'SyntheticMethod'.

В качестве исправления можно заменить вызов метода на непосредственную проверку равенства значений:

    void SyntheticMethod(Point x, object a)
    {
      if (a is Point pointA && pointA == x)
        ....
    }

Правило также применяется и к аргументам, типы которых являются generic-параметрами. Если значения такого параметра не ограничены ссылочными типами, то анализатор будет выдавать предупреждение. Пример:

    void SomeFunction<T>(T genericObject,
                         object someObject)
    {
      if (Object.ReferenceEquals(genericObject, someObject))
        ....
    }

Здесь 'genericObject' может быть экземпляром как ссылочного, так и значимого типа. Для значимых типов 'Object.ReferenceEquals' всегда будет возвращать 'false'. Такое поведение может оказаться неожиданным и нежелательным. Чтобы быть уверенным в том, что в метод не будут передаваться объекты значимых типов, нужно добавить соответствующее ограничение параметра:

    void SomeFunction<T>(T genericObject,
                         object someObject) where T : class
    {
      if (Object.ReferenceEquals(genericObject, someObject))
        ....
    }

Теперь на месте параметра может быть только ссылочный тип, и анализатор не будет выдавать предупреждение.

Рассмотрим ситуацию, в которой параметр ограничен интерфейсом:

    void SomeFunction<T>(T genericObject,
                         object someObject) where T : ICloneable
    {
      if (Object.ReferenceEquals(genericObject, someObject))
        ....
    }

Анализатор выдаст предупреждение на данный код, так как объекты значимых типов могут реализовывать интерфейсы. Таким образом, ограничение 'where T : ICloneable' не защищает от вызова метода для структур, и результат такого вызова может оказаться неожиданным.

Отдельно рассмотрим одну особенность, связанную с nullable-типами. Они являются значимыми, следовательно, их преобразование в 'Object' приведёт к упаковке. Однако упаковка объектов таких типов производится особым образом. Если переменной присвоено значение, то при упаковке в кучу попадёт не объект nullable-типа, а само значение. Если же значение не задано, то при упаковке будет получена нулевая ссылка.

В следующем примере 'x' имеет тип 'Nullable<Point>'. Вполне возможно, что в результате упаковки 'x' будет возвращена нулевая ссылка. В таком случае вызов 'Object.ReferenceEquals' вернёт 'true', если аргумент 'a' также будет равен 'null'.

    void SyntheticMethod(Point? x, object a)
    {
      if (Object.ReferenceEquals(x, a))
        ....
    }

Тем не менее, на этот код будет выдано предупреждение. Использование такого способа для проверки двух элементов на равенство 'null' выглядит подозрительно. Уместнее будет проверять наличие значений непосредственным сравнением с 'null' или с помощью свойства 'HasValue':

    void SyntheticMethod(Point? x, object a)
    {
      if (!x.HasValue && a == null)
        ....
    }

Дополнительные материалы по теме особенностей nullable-типов:

- Хорошо ли вы помните nullable value types? Заглядываем "под капот"
- Nullable value types (C# reference)
- Boxing and Unboxing (C# Programming Guide)

Данная диагностика классифицируется как:

CWE-686

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V3161.