>
>
>
V3149. Dereferencing the result of 'as'…


V3149. Dereferencing the result of 'as' operator can lead to NullReferenceException.

Анализатор обнаружил небезопасное разыменование результата приведения типа через оператор 'as'.

Рассмотрим пример:

void Foo()
{
  BaseItem a = GetItem();
  var b = a as SpecificItem;
  b.Bar();
}

Возвращаемое методом значение может иметь тип, отличный от типа, к которому мы его приводим. Тогда при приведении типа с помощью оператора 'as' в переменную 'b' будет записано значение null. Хотя при самом приведении типа ошибки не будет, при последующем использовании этой переменной без проверки произойдёт выброс исключения 'NullReferenceException'. Исправленный код будет выглядеть так:

void Foo()
{
  BaseItem a = GetItem();
  var b = a as SpecificItem;
  b?.Bar();
}

Если вы уверены, что runtime тип значения у переменной, приводимой с помощью оператора 'as', всегда может быть успешно приведён к указанному типу, то лучше воспользоваться оператором явного приведения типа:

void Foo()
{
  BaseItem a = GetItem();
  var b = (SpecificItem)a;
  b.Bar();
}

В этом случае, если в будущем поведение программы поменяется и метод 'GetItem' перестанет всегда возвращать значение, гарантированно приводимое к заданному типу, в месте приведения будет сгенерировано исключение 'InvalidCastException', что позволит сразу идентифицировать проблемное место в коде. Напротив, используя оператор 'as' в такой ситуации, можно получить 'NullReferenceException' далее по коду, когда приведённая неуспешно переменная, содержащая null, будет разыменована. Причём это может произойти далеко от места, где производится приведение типов, например в другом методе, что затруднит локализацию и исправление такой ошибки.

Также диагностика указывает на возможные опечатки при проверках типа:

void Foo()
{
  IDisposable a = GetItem();
  if(a is NonSpecificItem)
  {
    var b = a as SpecificItem;
    b.Bar();
  }
}

В данном примере типы SpecificItem и NonSpecificItem не связаны друг с другом и результатом приведения будет нулевой указатель. Для защиты от таких опечаток можно использовать возможности C# 7.0 и сделать проверку через Is Type Pattern синтаксис:

void Foo()
{
  IDisposable a = GetItem();
  if(a is NonSpecificItem item)
  {
    item.Bar();
  }
}

Приведём пример работы диагностики на коде из открытого проекта:

....
FuelDefinition = MyDefinitionManager.Static.GetPhysicalItemDefinition(FuelId);
MyDebug.AssertDebug(FuelDefinition != null);
....
String constraintTooltip = FuelDefinition.DisplayNameText;

В данном примере метод 'GetPhysicalItemDefinition' возвращает объект типа MyPhysicalItemDefinition, полученный из массива объектов базового типа 'MyDefinitionBase':

public MyPhysicalItemDefinition GetPhysicalItemDefinition(MyDefinitionId id)
{
  ....
  return m_definitions.m_definitionsById[id] as MyPhysicalItemDefinition;
}

При этом после вызова метода 'GetPhysicalItemDefinition' присутствует проверка результата приведения на null (MyDebug.AssertDebug), что указывает на потенциальную возможность получения из метода объекта неподходящего типа. Однако данная проверка будет работать только в Debug версии приложения. В Release версии неудачное приведение приведёт к разыменованию нулевой ссылки далее по ходу выполнения программы (FuelDefinition.DisplayNameText).

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