为啥 C# 编译器不抛出 null 的逻辑比较?

Posted

技术标签:

【中文标题】为啥 C# 编译器不抛出 null 的逻辑比较?【英文标题】:Why doesn't the C# compiler throw for logical comparisons of null?为什么 C# 编译器不抛出 null 的逻辑比较? 【发布时间】:2018-09-13 03:50:29 【问题描述】:

我昨天和一个朋友一起吃午饭,他们抱怨 C# 中的 null。他说null 是不合逻辑的。我决定测试他的说法,所以我测试了一些简单的逻辑命题:

Console.WriteLine(null == null); //True
//Console.WriteLine(null == !!null); //BOOM

Console.WriteLine(10 >= null); //False
Console.WriteLine(10 <= null); //False

Console.WriteLine(!(10 >= null)); //True
Console.WriteLine(!(10 <= null)); //True

检查相等性似乎很简单,这是我所期望的。然而,大于/小于语句是我觉得非常混乱的逻辑矛盾!这些不应该扔吗?否定操作如您所料抛出。

如果我尝试在 Ruby 或 Python 中使用 null 运行比较(除了相等性),我会收到一个类型错误,类似于“无法将数字与 nil 进行比较”。为什么 C# 不这样做?

【问题讨论】:

感谢阿伦!我看不出可空类型与逻辑命题有什么关系。什么意思? 这是标准行为,请参阅有关可空类型的文档:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/… 引入它们是为了简化与 SQL 数据库的交互(在 SQL 中,您有 3 值逻辑:true、false 和 null)跨度> 只是不要告诉你的朋友 javascriptundefinednull,或者NaN 不等于它自己。 很多解释取决于您认为 null 的含义。有多种解释是自洽的。 C# 倾向于将 null 视为“未知”的意思。什么是 10+ 未知数?好吧,这也是未知的,所以 10 + null == null 是有道理的。 C# 还将未知映射为 false。 10 顺便提个很好的第一个问题 - 欢迎来到这个网站。 【参考方案1】:

很好的问题。

尽量不要将null 视为特定值,而应将其视为“这里没什么可看的”。 documentation 将 null 定义为

null 关键字是一个表示空引用的文字,它不引用任何对象。

考虑到这一点,null 不是一个对象这一事实意味着经典的思想法则并不完全适用于它(或者,至少,不以与它相同的方式适用于它将适用于实际对象)。

话虽如此,10 &gt;= null10 &lt;= null 都是 false 的事实严格来说并不矛盾 - 毕竟,null 几乎什么都不是。如果你说10 &gt;= (some actual thing)10 &lt;= (some actual thing) 都是假的,那显然是矛盾的,但是如果没有一些实际的对象,你就不能完全有古典意义上的矛盾。亚里士多德从形而上学对法则的定义如下:

同一事物不可能同时属于和不属于同一个对象,并且在同一方面,以及所有其他可能制定的规范,让它们添加满足当地的反对意见...

所以,从某种意义上说,我们这里有点“漏洞”。至少在亚里士多德制定非矛盾定律时,它专门指的是对象。当然,此时对非矛盾律有多种解释。

现在,转向10 + null 的情况。我们可以说这和null + 10 是一样的,如果它更简单的话。那么,从某种意义上说,此时应该发生什么 - null 应该“吞掉” 10,还是应该说“10 +(什么都没有)真的应该等于 10”?老实说,除了说“嗯,这是一种设计选择”之外,从逻辑的角度来看,我没有一个非常令人信服的答案。我怀疑语言设计者想要区分 10 + null10 + 0,但我没有文档来证明这一点。 (确实,如果它们相同,那就有点奇怪了;毕竟 0 是可以从自然数构造的实际值,但 null 是“没有任何值”。

【讨论】:

谢谢!所以你说 null 是一个指针,它本身也代表什么,我认为这是在文档中,我同意!但我想知道为什么编译器不会在这种情况下抛出,因为无法进行比较。认为您可以从编程中删除逻辑定律只是为了解释空值,这也是令人困惑的。对我来说似乎有点倒退。 @Pookchop 我同意你的观点,实际上 - 你可以提出一个非常有力的案例,即编译器应该抛出一个异常来尝试做一些(可以说)不完全有意义的事情。跨度> 再次感谢:)。我刚刚检查了 Ruby 是如何做到的,如果你尝试做 10 &lt; nil,它会抛出,与 Python 相同。 @Pookchop 我认为这可能反映了您将在大多数生产系统中看到的情况:实际上,围绕null 的确切含义并没有太多共识或一致性,所以你最终会遇到很多奇怪的边缘情况。【参考方案2】:

我知道它在某处的语言规范中,这不是我有兴趣知道的。我有兴趣了解为什么这些逻辑违规似乎存在,或者我对逻辑规律的理解是否不正确。

编程语言不应该遵循哲学隐含的逻辑规则,无论如何,作为旨在解决具体行业/业务相关问题的编程语言。很多语言甚至不遵守数学思维的规则,比如代数表达式、范畴论等等,所以......

10 必须大于或小于 null ...不是吗?

不,“null 关键字是表示空引用的文字,不引用任何对象的文字”,并且根据C# 规则,编译器扫描可用运算符以查找提供的类型,如果其中一个参与者是null,表达式的结果将是null

寻找lifted operators

还有 10 + null 怎么可能是 null 呢?

见上文。

【讨论】:

因为任何包含空值的数学运算总是等于空 感谢@Tigran!我不会说亚里士多德的法律是“哲学暗示的”,它们是逻辑的基础,我认为这与我们如何解决问题有关,除非我误解了你? @Pookchop:编程语言比科学更“扎根”,更接近具体的业务问题(至少C#),有时甚至是科学。所以我不会在哲学、科学和编程之间进行比较,并试图解释最终会出现的任何差异,因为我们不是在比较苹果和苹果。 @Tigran 逻辑和哲学是不一样的,我不是在讨论哲学——我相信你提出来了。逻辑是科学和编程的基础,并且非常相关。 我认为这里的重点是 C# 中的“null”被设计为使用具有逻辑意义的“null”概念进行操作,只是一个不同的(在某些方面可能更实际有用的)概念你所期待的。这里的“null”概念本质上类似于 SQL 中使用的概念,并且在关系理论中具有相当强的逻辑基础。【参考方案3】:

如果您不将 'null' 视为一个值,它会有所帮助 - 这是一个虚无或缺少值的概念。这不仅仅是 C# - 你会在 SQL 中遇到同样的事情(NULL + 'asdf' 将导致 NULL。)

所以像 (NULL

警告您远离 NULL 的原因与逻辑无关。 这是一个很好的例子,说明了为什么 C# 中的 NULL 经常会导致错误/错误:

private NumericalAnalysis AnalyzeNumber(int someValue)

    if (someCondition)
        return null;
    return new NumericalAnalysis(someValue);

...好的,现在我们可以调用 AnalyzeNumber() 并开始使用 NumericalAnalysis:

NumericalAnalysis instance = AnalyzeNumber(3);
if (instance.IsPrime)
    DoSomething();

...哎呀!你刚刚遇到了一个错误。您调用了 AnalyzeNumber() 但在使用它之前没有检查返回值是否为 null。毕竟,AnalyzeNumber() 有时会返回一个空值——所以当 instance.IsPrime 被调用时,它会爆炸。 每次调用该函数时,您都必须执行“if (myVar == null)”检查 - 如果您忘记了,您只是引入了一个错误。

【讨论】:

你可以做if (instance?.IsPrime) DoSomething(); 但我不确定这是否对讨论有帮助:-) @David 有点像。我的意思是,在 C# 中,所有类都是可以为空的,所以没有真正的方法来使用“类型?”这种情况的风格。它会因为你试图在一个已经可以为空的类型上使用它而对你大喊大叫。 不确定你得到了什么,但我的例子不起作用,因为如果 instance 为 null 那么表达式的计算结果为 null,这使得条件计算为 null - 所以是的,我希望编译器应该冲我大喊大叫。【参考方案4】:

我不是 C# 语言设计者,所以(耸耸肩),但就个人而言,我认为这是一个原始类型系统故障,所以我的头脑可以接受它。

原因是从逻辑上讲,如果不进行任何转换,您就无法将确定性值(例如 int)与不确定性值(可为空的 int)进行比较。它们实际上是两种不同的类型,但现代编程语言只是尝试以自己的方式将它们混合在一起。

我还认为 null 是一件好事和有用的事情,因为我们从来没有完整的数据,也从来没有想要完整的数据,因为我们只需要它的一个子集来进行手头的活动。

【讨论】:

【参考方案5】:

我认为这是出于历史原因。在 C# 语言的 2.0 版中向 C# 添加可空值类型的主要动机是减轻在 ADO.NET 中使用可空 sql 列的痛苦。因此,可空数字的加法被设计为在 sql 中工作,等等。

【讨论】:

以上是关于为啥 C# 编译器不抛出 null 的逻辑比较?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这段代码不抛出 NullPointerException?

为啥设置 DataSource 时 ComboBox 不抛出异常?

为啥 JPA 重复持久方法不抛出异常?

为啥不抛出异常的代码允许捕获已检查的异常?

C++ STL栈问题:为啥栈为空时pop()不抛出异常?

为啥 C++ 中 std::mutex 的构造函数不抛出?