Почему проверка типа, не допускающего значения NULL, на значение NULL на самом деле компилируется в C # 8 - PullRequest
0 голосов
/ 17 июня 2020

Я пытаюсь использовать ссылочные типы, допускающие значение NULL, в C# 8.0. Я создал образец кода для его проверки, но что-то меня сбило с толку!

Насколько я понял, когда мы не добавляем ? к Type, это обрабатывается как не допускающее значения NULL. Тогда почему в методе Main () компилятор не жалуется, что я проверяю myClass против null? Разве к этому моменту компилятору не должно быть очевидно, что myClass не допускает значения NULL?

Обновление:

enter image description here

Я добавил в MyClass метод-заглушку. Как видите, на этом этапе компилятор знает, что myClass не равен нулю. Почему он допускает следующую нулевую проверку? Бессмысленно!

#nullable enable
using System;

namespace ConsoleApp4
{
    class Program
    {
        static void Main()
        {
            var service = new Service();
            MyClass myClass = service.GetMyClass();

            myClass.Write();

            // I am curious why this line compiles!
            if (myClass == null)
            {
                throw new ArgumentNullException();
            }
        }
    }

    public sealed class Service
    {
        private MyClass? _obj;

        public MyClass GetMyClass()
        {
            if (_obj == null)
            {
                _obj = new MyClass();
            }

            return _obj;
        }
    }

    public sealed class MyClass
    {
        public void Write()
        {

        }
    }
}

Ответы [ 2 ]

3 голосов
/ 17 июня 2020

Разве к этому моменту компилятору не должно быть очевидно, что myClass не допускает значения NULL?

Так оно и есть. Наведите указатель мыши на myClass на нулевой проверке, и всплывающая подсказка из анализа stati c сообщит вам, что myClass не является нулевым.

Итак, почему вы можете проверить его на нулевое значение? Потому что ты можешь. Это все еще ссылочный тип. Тот факт, что оно не будет нулевым, не аннотируется в типе.

Кроме того, обратите внимание, что он не сообщает вам, что исключение является недостижимым кодом. Почему это?

Ну, C# 8.0 анализ статических c ссылок, допускающих значение NULL, не надежен. Этого достаточно для большинства случаев. Однако время от времени появляются закономерности, которые они не распознают. Нужна помощь аннотаций и оператора, допускающего нуль (!). Что вы можете использовать, чтобы сообщить компилятору, когда вам будет лучше. Однако это оставляет место для лжи компилятору:

public MyClass GetMyClass()
{
    return _obj!;
}

Точно так же вы можете проверить значение null в том, что, согласно компилятору stati c, не может иметь значение null. Потому что вам виднее:

public sealed class Service
{
    private MyClass? _obj;

    public MyClass GetMyClass()
    {
        return _obj!;
    }
}

internal static class Program
{
    private static void Main()
    {
        var service = new Service();
        var myClass = service.GetMyClass();

        // I am curious why this line compiles!
        if (myClass == null)
        {
            throw new ArgumentNullException();
        }
    }
}

Конечно, я бы не рекомендовал на самом деле go вперед и l ie компилятору. Тем не менее, если вы получаете параметры от третьей стороны, рекомендуется выполнять нулевые проверки. Точно так же при использовании publi c API, когда вы не можете доверять реализации.


Я также напомню вам, что C# 8.0 обнуляемые ссылки - это функция, которая должна была постоянно разворачиваться в большие устаревшие кодовые базы. Это не было предназначено для того, чтобы помешать им компилироваться, когда вы включили это в одной части, а не в другой. На самом деле, насколько я могу судить, в худшем случае он выдаст предупреждения. Не компилировать нулевую проверку было бы go против этого.


Почему бы не выводить предупреждение о нулевой проверке?

Что ж, в этом случае Рослин уступает место разработчику. Если вы попытаетесь использовать myClass внутри тела условного оператора и навести на него курсор, он сообщит вам, что он может иметь значение null. Что, конечно, было бы необходимо для ввода условного оператора в первую очередь.

Кроме того, удалите throw и используйте myClass после условного. Анализ stati c также скажет, что myClass может иметь значение null после условия. Таким образом, Roslyn использует нулевую проверку как намек на то, что переменная действительно может иметь значение NULL.

Почему они решили использовать ее как подсказку вместо того, чтобы выдавать предупреждение? Этого я не знаю. Мне не удалось найти обсуждение на dotnet/csharplang или dotnet/roslyn на github.

0 голосов
/ 17 июня 2020

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

Для всех, кто заинтересован, также проверьте это:

В C# 8, как мне обнаружить невозможные проверки на null?

Для тех, кто не знаком с DDD, ValueObject является базовым классом для наших неизменяемых объектов.

#nullable enable
using System;

namespace ConsoleApp4
{
    internal static class Program
    {
        private static void Main()
        {
            var service = new Service();
            MyClass myClass = service.GetMyClass();

            myClass.Write();

            // Compiler now will not compile! mcClass can never be null!
            if (myClass == null)
            {
                throw new ArgumentNullException();
            }
        }
    }

    public sealed class Service
    {
        private MyClass? _obj;

        public MyClass GetMyClass()
        {
            return _obj ?? (_obj = new MyClass());
        }
    }

    public sealed class MyClass : ValueObject<MyClass>
    {
        public void Write()
        {
        }

        protected override bool EqualsCore(MyClass other)
        {
            throw new NotImplementedException();
        }

        protected override int GetHashCodeCore()
        {
            throw new NotImplementedException();
        }
    }
}


#nullable enable

namespace ConsoleApp4
{
    public abstract class ValueObject<T> where T : ValueObject<T>
    {
        public sealed override bool Equals(object obj)
        {
            return obj is T valueObject && EqualsCore(valueObject);
        }

        public sealed override int GetHashCode()
        {
            return GetHashCodeCore();
        }

        protected abstract bool EqualsCore(T other);

        protected abstract int GetHashCodeCore();

        public static bool operator ==(ValueObject<T> a, ValueObject<T> b)
        {
            return a.Equals(b);
        }

        public static bool operator !=(ValueObject<T> a, ValueObject<T> b)
        {
            return !(a == b);
        }
    }
}
...