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

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

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

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

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

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

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

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

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

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

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

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

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

```cpp
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'\.

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

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

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

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

* [Хорошо ли вы помните nullable value types? Заглядываем "под капот"](https://pvs-studio.ru/ru/blog/posts/csharp/0772/)
* [Nullable value types \(C\# reference\)](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types)
* [Boxing and Unboxing \(C\# Programming Guide\)](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing)