【问题标题】:Why checking a Non-nullable type against null actually compiles in C#8为什么在 C#8 中针对 null 检查不可为空的类型实际上可以编译
【发布时间】:2020-10-07 04:10:57
【问题描述】:

我正在尝试在 C# 8.0 中使用可空引用类型。我已经设置了一个示例代码来测试它,但有些事情让我感到困惑!

据我了解,当我们? 添加到Type 时,它被视为不可为空。那么为什么在 Main() 方法中编译器没有抱怨我正在检查 myClassnull?在这一点上,编译器不应该很明显myClass 不可为空吗?

更新:

我向 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()
        {

        }
    }
}

【问题讨论】:

  • MyClass 是一个class,因此是一个引用类型。无论是否使用? 运算符声明,它仍然可以为空。
  • @BarryO'Kane C# 8 引入了可为空的引用类型,事情做了一些改变。 OP 用特定版本明确标记了问题
  • 无法分配 null 值,但我很难在规范中找到无法比较 带有null 值。
  • @BarryO'Kane 请参阅:Nullable reference types
  • @cassandrad:请参阅 OP 代码顶部的第一条指令行。

标签: c# nullable c#-8.0 non-nullable


【解决方案1】:

此时编译器不应该很明显 myClass 不可为空吗?

确实如此。将鼠标悬停在空检查上的 myClass 上,静态分析的工具提示会告诉您 myClass 不为空。

那么,为什么你可以检查它是否为空?因为你能。它仍然是一个引用类型。类型中没有注释它不会为 null 的事实。

此外,请注意它并没有告诉您异常是不可访问的代码。这是为什么呢?

好吧,C# 8.0 可空引用静态分析并非万无一失。对于大多数情况来说已经足够了。但是,有时会出现他们无法识别的模式。需要注释和容错运算符 (!) 的帮助。当你知道得更好时,你可以用它来告诉编译器。但是,这些给编译器撒谎的空间:

public MyClass GetMyClass()
{
    return _obj!;
}

同样,您可以在根据静态编译器不能为 null 的内容中检查 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();
        }
    }
}

当然,我不建议实际上继续对编译器撒谎。但是,如果您从第三方接收参数,建议仍然进行空检查。同样,在使用公共 API 时,当您无法信任实现时。


我还要提醒您,C# 8.0 可空引用是一项旨在在大型遗留代码库中稳步推出的功能。当您在某些部分而不是在其他部分启用它时,它并不打算阻止它们编译。事实上,据我所知,它在最坏的情况下会发出警告。使 null 检查不编译会违背这一点。


为什么不对 null 检查发出警告?

好吧,在这种情况下,Roslyn 是在听从开发人员的意见。如果您尝试在条件主体内使用myClass,并将鼠标悬停在它上面,它会告诉您它可以在那里为空。当然,首先要输入条件。

此外,删除throw 并在条件后使用myClass。静态分析还会说myClass在条件之后可以为空。因此,Roslyn 使用空检查作为变量实际上可以为空的提示。

为什么他们选择将其用作提示而不是发出警告?我不知道。我在 github 上的 dotnet/csharplangdotnet/roslyn 上找不到有关它的讨论。

【讨论】:

  • 谢谢 Theraot,这就是我的意思。我希望编译器在这里发出一个小警告,至少提醒我不需要我在这里做的空检查!它已经拥有执行此操作所需的信息。
【解决方案2】:

为了将来参考,当空检查是多余的时,我可以强制编译器实际上不编译。

有兴趣的朋友也可以看看这个:

In C# 8, how do I detect impossible null checks?

对于不熟悉 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);
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-19
    • 2012-01-03
    • 2012-12-03
    • 2019-08-21
    • 1970-01-01
    相关资源
    最近更新 更多