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).
Данная диагностика классифицируется как: