Почему я не получаю предупреждение о возможном разыменовании нулевого значения в C # 8 с членом класса структуры? - PullRequest
6 голосов
/ 17 октября 2019

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

public class ExampleClassMember
{
    public int Value { get; }
}

public struct ExampleStruct
{
    public ExampleClassMember Member { get; }
}

public class Program
{
    public static void Main(string[] args)
    {
        var instance = new ExampleStruct();
        Console.WriteLine(instance.Member.Value);  // expected warning here about possible null dereference
    }
}

Когда instance инициализируется конструктором по умолчанию, instance.Member устанавливается в значение по умолчанию ExampleClassMember, которое null. Таким образом, instance.Member.Value будет выбрасывать NullReferenceException во время выполнения. Поскольку я понимаю обнаружение обнуляемости C # 8, я должен получить предупреждение компилятора об этой возможности, но я не знаю;почему это?

Ответы [ 2 ]

11 голосов
/ 17 октября 2019

Обратите внимание, что для звонка на Console.WriteLine() нет никаких причин. Свойство ссылочного типа не может иметь тип NULL, поэтому компилятору не нужно предупреждать, что оно может быть нулевым.

Вы можете утверждать, что компилятор должен предупреждать о ссылке в самом struct. Это казалось бы разумным для меня. Но это не так. Похоже, это лазейка, вызванная инициализацией по умолчанию для типов значений, то есть всегда должен быть конструктор по умолчанию (без параметров), который всегда просто обнуляет все поля (нули для полей ссылочного типа, нули для числовых типов и т. Д.). ).

Я называю это лазейкой, потому что теоретически необнуляемые эталонные значения должны фактически быть ненулевыми! Duh. :)

Эта лазейка, по-видимому, решается в этой статье блога: Представление ссылочных типов, допускающих обнуляемость, в C #

Избегание нулей До сих пор предупреждения касались защитыnull в обнуляемых ссылках от разыменования. Другая сторона медали состоит в том, чтобы вообще не иметь нулевых значений в ненулевых ссылках.

Есть несколько способов, которыми могут возникнуть нулевые значения, и большинство из них стоит предупредить, в то время как параони вызовут еще одно «море предупреждений», которого лучше избегать:…

  • Использование конструктора по умолчанию для структуры, которая имеет поле ненулевого ссылочного типа. Это подлый, так как конструктор по умолчанию (который обнуляет структуру) может даже неявно использоваться во многих местах. Вероятно, лучше не предупреждать [выделение шахты - PD] , иначе многие существующие типы структур станут бесполезными.

Другими словамиДа, это лазейка, но нет, это не ошибка. Разработчики языка знают об этом, но предпочли исключить этот сценарий из предупреждений, потому что иначе было бы нецелесообразно, учитывая, как работает struct инициализация.

Обратите внимание, что это также соответствуетболее широкая философия за особенностью. Из той же статьи:

Итак, мы хотим, чтобы он жаловался на ваш существующий код. Но не противно. Вот как мы собираемся попытаться достичь этого баланса:...

Гарантированной нулевой безопасности не существует [выделено мной - PD] , даже если вы отреагируете и устраните все предупреждения. В анализе есть много пробелов по необходимости, а также по выбору.

К этому последнему пункту: иногда предупреждение является «правильным», но оно срабатывает все время на существующихкод, даже когда он на самом деле написан нулевым безопасным способом. В таких случаях мы будем ошибаться с точки зрения удобства, а не правильности. Мы не можем создавать «море предупреждений» в существующем коде: слишком много людей просто отключили бы предупреждения и никогда не извлекли бы из них пользу.

Также обратите внимание, что такая же проблема существует с массивами номинальнонеобнуляемые ссылочные типы (например, string[]). При создании массива все ссылочные значения равны null, и все же это допустимо и не будет выдавать никаких предупреждений.

Так много для объяснения, почему вещи такие, какие есть. Тогда возникает вопрос, что с этим делать? Это намного более субъективно, и я не думаю, что есть правильный или неправильный ответ. Тем не менее ...

Я лично буду относиться к своим struct типам в каждом конкретном случае. Для тех, где intent на самом деле является обнуляемым ссылочным типом, я бы применил аннотацию ?. Иначе я бы не стал.

Техникаlly, каждое значение ссылки в struct должно быть «обнуляемым», то есть включать аннотацию ? с именем типа. Но, как и во многих аналогичных функциях (например, async / await в C # или const в C ++), это имеет «заразительный» аспект, в котором вам нужно будет либо переопределить эту аннотацию позже (с аннотацией !),или включите явную проверку нуля, или только когда-либо присвойте это значение другой переменной типа ссылочного значения, допускающей значение nullable.

Для меня это во многом лишает цели включения ссылочных типов, допускающих значение null. Поскольку для таких членов типов struct в любом случае потребуется обработка в специальном случае в любом случае, и поскольку единственный способ действительно безопасно обрабатывать его, при этом все еще имея возможность использовать ненулевые ссылочные типы, состоит в установке нуляпроверяет везде, где вы используете struct, я чувствую, что разумным вариантом реализации является принятие того, что когда код инициализирует struct, именно код должен сделать это правильно и убедиться, что элемент ненулевого ссылочного типа действительноинициализируется ненулевым значением.

Этому можно помочь, предоставив «официальные» средства инициализации, такие как конструктор не по умолчанию (т. е. конструктор с параметрами) или фабричный метод. Тем не менее всегда будет риск использования конструктора по умолчанию или его отсутствия вообще (как при выделении массива), но, предоставляя удобные средства для правильной инициализации struct, это будет стимулировать код, использующий его, чтобы избежать нулевых ссылокв ненулевых переменных.

Тем не менее, если вы хотите 100% -ную безопасность по отношению к обнуляемым ссылочным типам, то ясно, что правильный подход для этой конкретной цели - всегда аннотировать каждый элемент ссылочного типа в struct с ?. Это означает каждое поле и каждое автоматически реализуемое свойство, а также любой метод или метод получения свойства, которые напрямую возвращают такие значения или произведение таких значений. Тогда потребляющий код должен будет включать проверки на нуль или оператор прощения на ноль в каждой точке, где такие значения копируются в ненулевые переменные.

1 голос
/ 17 октября 2019

В свете превосходного ответа @ peter-duniho кажется, что по состоянию на октябрь 2019 года лучше всего помечать все элементы, не относящиеся к типу значений, как обнуляемую ссылку.

#nullable enable
public class C
{
    public int P1 { get; } 
}

public struct S
{
    public C? Member { get; } // Reluctantly mark as nullable reference because
                              // https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/
                              // states:
                              // "Using the default constructor of a struct that has a
                              // field of nonnullable reference type. This one is 
                              // sneaky, since the default constructor (which zeroes 
                              // out the struct) can even be implicitly used in many
                              // places. Probably better not to warn, or else many
                              // existing struct types would be rendered useless."
}

public class Program
{
    public static void Main()
    {
        var instance = new S();
        Console.WriteLine(instance.Member.P1); // Warning
    }
}
...