为什么在.NET中
null >= null
解析为false,但是
null == null
是否解析为true?
换句话说,为什么null >= null
不等同于null > null || null == null
?
有没有人知道官方答案?
为什么在.NET中
null >= null
解析为false,但是
null == null
是否解析为true?
换句话说,为什么null >= null
不等同于null > null || null == null
?
有没有人知道官方答案?
这个行为在C#规范(ECMA-334)的第14.2.7节中定义(我已经强调了相关部分):
对于关系运算符
< > <= >=
如果操作数类型都是非空值类型,且结果类型为
bool
,那么就存在运算符的提升形式。提升形式是通过在每个操作数类型上添加单个?
修饰符来构建的。如果一个或两个操作数都是null
,则提升运算符会产生值false
。否则,提升运算符会解包操作数并应用基础运算符以产生bool
结果。特别地,这意味着关系的通常规律不成立;
x >= y
并不意味着!(x < y)
。详细说明
有些人问为什么编译器首先将
int?
视为提升运算符的类型。现在我们来看看这其中的原因。 :)我们从 14.2.4,即“二进制运算符重载决议”,开始讲起。这里详细描述了要遵循的步骤。
首先,检查用户定义的运算符是否合适。这是通过检查
>=
两侧类型定义的运算符来完成的...这也就引发了一个问题:null
的类型是什么!实际上,null
字面量在没有被赋予类型之前根本没有类型,它只是 “null 字面量”。按照 14.2.5 节的指示,我们发现这里没有适合的运算符,因为 null 字面量没有定义任何运算符。此步骤指示我们检查预定义运算符集的适用性。(由于双侧都不是枚举类型,因此此节还排除了枚举。)相关的预定义运算符列在 14.9.1 至 14.9.3 中,这些运算符都是基本数值类型的运算符,以及这些运算符的提升版本(请注意,
string
的运算符不包括在此处)。最后,我们必须使用这些运算符和 14.4.2 中的规则执行重载决议。
实际上执行此决议将非常繁琐,但幸运的是有一个快捷方式。在 14.2.6 下,给出了重载决议结果的信息性示例,其中指出:
...考虑二进制 * 运算符的预定义实现:
int operator *(int x, int y); uint operator *(uint x, uint y); long operator *(long x, long y); ulong operator *(ulong x, ulong y); void operator *(long x, ulong y); void operator *(ulong x, long y); float operator *(float x, float y); double operator *(double x, double y); decimal operator *(decimal x, decimal y);
当对这组运算符应用重载解析规则(§14.4.2)时,其效果是从操作数类型中存在隐式转换的第一个运算符被选择。
由于两个值都是
null
,我们可以立即排除所有未提升的运算符。这样,就只剩下了所有基元数值类型上的提升后的数值运算符。然后,利用前面的信息,我们选择第一个存在隐式转换的运算符。由于 null 文字可以隐式转换为可空类型,而
int
的可空类型是存在的,我们从列表中选择第一个运算符,即int? >= int?
。
null >= null
会变成 (int?)null >= (int?)null
,从而使其符合上述“提升形式”,我无法点赞此答案。 - user166390null >= null
会给出有关与int比较的警告的问题?-- 与我刚才所做的分析相同。非空值类型上的>=
运算符不适用,并且剩下的运算符中int?上的运算符最好。没有关于bool?或string的>=
运算符定义,因此没有歧义错误。编译器正确地将运算符分析为可空int的比较。null == null
的行为是由规范(§14.9.6)明确定义的:“如果类型A和B都是null类型,则不执行重载解析,并且结果是一个常量true或false,如§14.9中所指定的那样。” - porgesnull
被隐式地视为 int?
类型。Console.WriteLine(null == null); // true
Console.WriteLine(null != null); // false
Console.WriteLine(null < null); // false*
Console.WriteLine(null <= null); // false*
Console.WriteLine(null > null); // false*
Console.WriteLine(null >= null); // false*
Visual Studio 提供了一个警告:
*与 'int?' 类型的 null 进行比较总是产生 'false'
可以通过以下代码进行验证:
static void PrintTypes(LambdaExpression expr)
{
Console.WriteLine(expr);
ConstantExpression cexpr = expr.Body as ConstantExpression;
if (cexpr != null)
{
Console.WriteLine("\t{0}", cexpr.Type);
return;
}
BinaryExpression bexpr = expr.Body as BinaryExpression;
if (bexpr != null)
{
Console.WriteLine("\t{0}", bexpr.Left.Type);
Console.WriteLine("\t{0}", bexpr.Right.Type);
return;
}
return;
}
PrintTypes((Expression<Func<bool>>)(() => null == null)); // constant folded directly to bool
PrintTypes((Expression<Func<bool>>)(() => null != null)); // constant folded directly to bool
PrintTypes((Expression<Func<bool>>)(() => null < null));
PrintTypes((Expression<Func<bool>>)(() => null <= null));
PrintTypes((Expression<Func<bool>>)(() => null > null));
PrintTypes((Expression<Func<bool>>)(() => null >= null));
输出:
() => True
System.Boolean
() => False
System.Boolean
() => (null < null)
System.Nullable`1[System.Int32]
System.Nullable`1[System.Int32]
() => (null <= null)
System.Nullable`1[System.Int32]
System.Nullable`1[System.Int32]
() => (null > null)
System.Nullable`1[System.Int32]
System.Nullable`1[System.Int32]
() => (null >= null)
System.Nullable`1[System.Int32]
System.Nullable`1[System.Int32]
为什么?
这对我来说似乎很合理。首先,这是C# 4.0规范的相关部分。
空字面量 §2.4.4.6:
空字面量可以隐式转换为引用类型或可空类型。
二进制数字提升 §7.3.6.2:
二进制数字提升适用于预定义的+、-、*、/、%、&、|、^、==、!=、>、<、>=和<=二元运算符的操作数。二进制数字提升隐式地将两个操作数转换为一个公共类型,该公共类型在非关系运算符的情况下也成为操作的结果类型。二进制数字提升由按照以下规则应用的规则组成:
• 如果任一操作数为decimal类型,则将另一个操作数转换为decimal类型,或者如果另一个操作数为float或double类型,则出现绑定时错误。
• 否则,如果任一操作数为double类型,则将另一个操作数转换为double类型。
• 否则,如果任一操作数为float类型,则将另一个操作数转换为float类型。
• 否则,如果任一操作数为ulong类型,则将另一个操作数转换为ulong类型,或者如果另一个操作数为sbyte、short、int或long类型,则出现绑定时错误。
• 否则,如果任一操作数为long类型,则将另一个操作数转换为long类型。
• 否则,如果任一操作数为uint类型且另一个操作数为sbyte、short或int类型,则两个操作数都转换为long类型。
• 否则,如果任一操作数为uint类型,则将另一个操作数转换为uint类型。
• 否则,两个操作数都转换为int类型。
可提升运算符 §7.3.7:
可提升运算符允许预定义和用户定义的操作符用于非空值类型的可空形式。可提升运算符由满足某些要求的预定义和用户定义的操作符构成,如下所述:
• 对于关系运算符
< > <= >=
如果操作数类型都是非空值类型并且结果类型为bool,则存在运算符的可提升形式。通过在每个操作数类型中添加单个?修饰符来构造可提升形式。如果一个或两个操作数为null,则可提升运算符产生false值。否则,可提升运算符展开操作数并应用基础运算符以产生bool结果。
单独的 null 值并没有明确的类型,它的类型是由赋值对象推断出来的。但是在这里没有进行任何赋值操作。只考虑具有语言支持(即关键字)的内置类型,object
或任何可空类型都是不错的选择。然而,object
不可比较,因此被排除在外。这就留下了可空类型作为良好的选择。但是哪种类型呢?由于左右操作数都没有指定类型,它们默认转换为(可空)int
。由于两个可空值都为 null,所以返回 false。
编译器似乎将null视为整数类型。
VS2008备注:"Comparing with null of type 'int?' always produces 'false'"
提示:与“int?”类型的空值进行比较始终会产生“false”。
null >= null
=> (int?)null >= (int?)null
,但是为什么? - user166390>= null
永远为false,并将您的表达式替换为常量值false
。请看以下示例:using System;
class Example
{
static void Main()
{
int? i = null;
Console.WriteLine(i >= null);
Console.WriteLine(i == null);
}
}
class Example
{
private static void Main()
{
int? i = new int?();
Console.WriteLine(false);
Console.WriteLine(!i.HasValue);
}
}
null >= null
总是为假?这意味着 null >= null
不等同于 null == null || null > null
。 - R. Martinho Fernandes>= null
始终为false。因此,null >= null
是false。你是正确的(“null >= null不等同于null == null || null > null”);如果这是真的,那么你就不会看到这种行为! - Kirk BroadhurstFunc<int?> f = () => { Console.Write("foo"); return 42; }; Console.Write(null >= f());
会输出"fooFalse"。我好奇的是为什么">= null总是false",而不是">总是false,并且>=在其他地方像> || ==一样运作"。 - R. Martinho Fernandesa.Value >= b.Value
没有被评估,因为它在 &&
之后,并且 HasValue
没有被满足。如果 >=
部分没有被评估,那不就是短路了吗? - Kirk BroadhurstFunc<int?> f = () => { throw new Exception(); }; a >= f()
会抛出异常,但是Func<bool> f = () => { throw new Exception(); }; false && f()
不会。&&可能只评估一个操作数,但是>=总是评估两个操作数。这就是一个短路,而另一个不是的原因。 - R. Martinho Fernandesnull >= null
我收到了一个警告信息:
与 'int?' 类型的 null 进行比较 总是产生 'false'
但我想知道为什么它被强制转换成了 int。
null >= null
=> (int?)null >= (int?)null
,但是为什么? - user166390这不是“官方”答案,这只是我的最佳猜测。但如果你正在处理可空整数并进行比较,那么如果你处理的是两个为null的“int?”时,你很可能希望比较返回false。这样,如果返回true,你就可以确定你实际上比较了两个整数,而不是两个空值。这只是消除了单独的空值检查的需要。
话虽如此,如果它不是你期望的行为,它有潜在的混淆!
null
时,>
和<
没有任何意义。 null
是值的缺失,因此只等于另一个也没有值的变量。既然>
和<
没有意义,那么这种情况也适用于>=
和<=
。(X)null >= (X)null
成立,其中X定义了>=
(和<=
)。因此,这是null >= null
的特定情况(与非法的(object)null >= (object)null
不同)。 - user166390他们似乎将C和SQL的范例混合在一起了。
在可空变量的上下文中,如果没有任何值被知道,null == null应该返回false,因为相等性没有意义,然而,如果针对整个C#语言这样做,会在比较引用时引发问题。
false
的原因,如果操作数中有任何一个为null。请记住,直到C#2才引入了Nullable<T>
,此时开发人员已经习惯了对于引用类型的if (myObj == null)
习惯用法。对于空可空类型而言,使用相同的习惯用法会很奇怪(并且打破现有的引用类型行为是不可能的)。 - LukeH简短的回答是“因为这些运算符在规范中是这样定义的”。
来自ECMA C# spec第8.19节:
“==”和“!=”的提升形式将两个空值视为相等,将空值视为与非空值不相等。“<”,“>”,“<=”,和“>=”的提升形式如果一个或两个操作数为空,则返回false。
null >= null
"被提升"? - user166390
(object)null >= (object)null
是一个编译错误(好吧,它没有定义>=
,当然会出错)。IL中到底发生了什么?当然还有(int?)null >= (int?)null
等,这显示了上面的行为。 - user166390Nullable<T>
。 - user166390