Ben*_*Ben 39 .net c# null comparison-operators
为什么在.NET中呢?
null >= null
Run Code Online (Sandbox Code Playgroud)
解析为假,但是
null == null
Run Code Online (Sandbox Code Playgroud)
解析为真?
换句话说,为什么不null >= null等同于null > null || null == null?
有人有正式答案吗?
por*_*ges 32
此行为在C#规范(ECMA-334)的第14.2.7节中定义(我已突出显示相关部分):
对于关系运算符
Run Code Online (Sandbox Code Playgroud)< > <= >=如果操作数类型都是非可空值类型且结果类型是,则存在提升形式的运算符
bool.通过向?每个操作数类型添加单个修饰符来构造提升形式.如果一个或两个操作数都是,则提升的运算符产生值falsenull.否则,提升的运算符会解包操作数并应用基础运算符来生成bool结果.
特别是,这意味着通常的关系法则不成立; x >= y并不意味着!(x < y).
有些人问为什么编译器首先决定这是一个提升的运算符int?.我们来看一下.:)
我们从14.2.4开始,'二元运算符重载决策'.这详细说明了要遵循的步骤.
首先,检查用户定义的运算符的适用性.这是通过检查由>=......的每一侧的类型定义的运算符来完成的,这提出了什么类型的问题null!该null文本实际上没有任何类型的,直到给定的一个,它只是"空文本".通过遵循14.2.5下的指示,我们发现这里没有适合的运算符,因为null literal没有定义任何运算符.
此步骤指示我们检查适用性的预定义运算符集.(此部分也排除了枚举,因为任何一方都不是枚举类型.)相关的预定义运算符在第14.9.1至14.9.3节中列出,它们都是原始数字类型的运算符,以及提升的版本这些运算符(注意string这里不包括s运算符).
最后,我们必须使用这些运算符和14.4.2中的规则来执行重载决策.
实际上执行这个决议将是非常乏味的,但幸运的是有一个捷径.在14.2.6下,有一个关于重载分辨率结果的信息性例子,其中说明:
...考虑二元*运算符的预定义实现:
Run Code Online (Sandbox Code Playgroud)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?.
Eri*_*ert 27
许多答案都符合规范.事实证明是一个不寻常的事件转变,C#4规范并没有证明两个空文字比较中特别提到的行为.事实上,严格阅读规范说"null == null"应该会产生歧义错误!(这是因为在准备C#3时清理C#2规范期间出现了编辑错误;规范作者并不打算将其视为非法.)
如果你不相信我,请仔细阅读规范.它表示在int,uint,long,ulong,bool,decimal,double,float,string,enums,delegates和objects上定义了相等运算符,以及所有值类型运算符的提升到可空版本.
现在,我们立即遇到了问题; 这个集合无限大.在实践中,我们不会在所有可能的委托和枚举类型上形成所有运算符的无限集合.需要在此修复规范,以注意添加到候选集的枚举和委托类型上的唯一运算符是枚举或委托类型的那些,它们是任一参数的类型.
因此,让我们将枚举和委托类型留在其中,因为这两个参数都没有类型.
我们现在有一个重载解决问题; 我们必须首先消除所有不适用的运营商,然后确定最适合的运营商.
显然,在所有非可空值类型上定义的运算符都不适用.这使得运算符处于可空值的类型,字符串和对象上.
我们现在可以因为"更好"的原因而消除一些.更好的运营商是具有更具体类型的运营商.诠释?比任何其他可以为空的数字类型更具体,因此所有这些都被消除了.字符串比对象更具体,因此消除了对象.
这为string,int留下了相等的运算符?和布尔?作为适用的运营商.哪一个是最好的? 没有一个比另一个好.因此,这应该是一个模糊错误.
为了通过规范证明这种行为,我们必须修改规范,注意"null == null"被定义为具有字符串相等的语义,并且它是编译时常量true.
我昨天刚刚发现了这个事实; 你应该问多少奇怪的事.
回答其他答案中提出的问题,为什么null >= null会给出关于int比较的警告? - 好吧,应用与我刚才相同的分析.>=非可空值类型的运算符是不适用的,而对于剩下的运算符,int上的运算符?是最好的.没有歧义错误,>=因为>=bool上没有定义运算符?或字符串.编译器正在将运算符正确地分析为两个可为空的int的比较.
要回答有关为什么运算符处于空值(而不是文字)的特定异常行为的更一般性问题,请参阅我对重复问题的回答.它清楚地解释了证明这一决定的设计标准.简而言之:对null的操作应该具有"我不知道"操作的语义.您不知道的数量是否大于或等于您不知道的其他数量?唯一明智的答案是"我不知道!" 但我们需要把它变成一个布尔,而明智的布尔是"虚假的".但是在比较相等时,大多数人认为null应该等于null,即使比较两个你不知道平等的事情也应该导致"我不知道".这个设计决策是将许多不良结果相互抵消以找到使该特征有效的最不良结果的结果; 它确实使语言有些不一致,我同意.
编译器推断,在比较运算符的情况下,null隐式输入为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*
Run Code Online (Sandbox Code Playgroud)
Visual Studio提供警告:
*与'int?'类型的null比较 总是产生'假'
这可以使用以下代码进行验证:
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));
Run Code Online (Sandbox Code Playgroud)
输出:
() => 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]
Run Code Online (Sandbox Code Playgroud)
为什么?
这对我来说似乎合乎逻辑.首先,这里是C#4.0规范的相关部分.
nullliteral§2.4.4.6:
null-literal可以隐式转换为引用类型或可空类型.
二进制数字促销§7.3.6.2:
对于预定义的+, - ,*,/,%,&,|,^,==,!=,>,<,> =和<=二元运算符的操作数,会发生二进制数字提升.二进制数字提升隐式地将两个操作数转换为公共类型,在非关系运算符的情况下,它也成为操作的结果类型.二进制数字促销包括按照它们在此处显示的顺序应用以下规则:
•如果任一操作数的类型为十进制,则另一个操作数将转换为十进制类型,或者如果另一个操作数的类型为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,则存在运算符的提升形式.提升形式是通过添加一个?每个操作数类型的修饰符.如果一个或两个操作数为空,则提升的运算符将生成值false.否则,提升的运算符解包操作数并应用基础运算符以产生bool结果.
单独的null-literal实际上没有类型.它是由它分配的内容推断出来的.但是,这里没有任何转让.只考虑具有语言支持的内置类型(带有关键字的内置类型)object或任何可以为空的类型将是一个很好的候选者.但是object没有可比性,所以它被排除在外.这使得可空类型成为好的候选人.但是哪种?由于左操作数和右操作数都没有指定的类型,因此它们int默认转换为(可为空).由于两个可空值都为null,因此返回false.
| 归档时间: |
|
| 查看次数: |
15050 次 |
| 最近记录: |