在C#中,“!=”和“is not”有区别吗?

102

这是:

if(x != y)
{

}

与此不同:

if (x is not y)
{

}

还是说这两种情况没有任何区别吗?


3
是的,从C# 9开始,还有orand关键字。详情请见https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#logical-patterns - StriplingWarrior
41
约翰·吴(John Wu)在将近两年前的圣诞聚会上坐得离 VB 太近了,结果染上了一种叫做 C#ViB-19 的东西,自那以后就再也没有恢复过来了。 - Caius Jard
17
我知道一开始看起来很傻,但现在我喜欢它:能够使用“x是1或2或3”比使用“(x == 1 || x == 2 || x == 3)”要好得多,并且作为额外的奖励:当“x”是一个表达式而不是一个值时,“is”运算符只评估“x”一次,而“(x == 1 || x == 2 || x == 3)”会导致对“x”的3次评估。 - Dai
3
@Dai - 我对编译器并不了解(如果有的话),但在像 ( x == 1 || x == 2 || x ==3 ) 这样简单的情况下,编译器是否能够将其优化为单个评估,然后以与 x is 1 or 2 or 3 相同的方式进行比较?我绝对不会怀疑你 - 我只是为了自己的教育而问一下。 - Spratty
3
对于简单字段或局部变量,通常没有区别,但对于其他类型的表达式(例如计算属性),第一种情况不能总是安全地优化为单个评估,因为评估 x 可能具有副作用,编译器无法知道代码是否打算在每次比较后重新评估表达式。 - castholm
显示剩余6条评论
4个回答

159

比较表:

运算符 != is not
原始目的 值不相等 否定模式匹配
可执行值不相等
可执行否定模式匹配
可以在左操作数上调用implicit运算符
可以在右操作数上调用implicit运算符 1
自身运算符 2
可重载
自从 C# 1.0 C# 9.03
值类型空比较分支省略4 [需要引用]5
不可能的比较 错误 警告
左操作数 任何表达式 任何表达式
右操作数 任何表达式 仅常量表达式6
语法 <any-expr> != <any-expr> <any-expr> is [not] <const-expr> [or|and <const-expr>]*
以及更多

常见例子:

例子 != is not
非空 x != null x 不是 null
值不相等的例子 x != 'a' x 不是 'a'
运行时类型(不)匹配 x.GetType() != typeof(Char) x 不是 Char 类型7
SQL x NOT IN ( 1, 2, 3 ) x != 1 && x != 2 && x != 3 x 不是 1 或 2 或 3

回答楼主的问题,直接而具体地说:
if( x != y ) { }
// vs:
if( x is not y ) { }

如果x是整数值类型(例如int/ Int32),而y是一个const-expression(例如const int y = 123;),那么不会有区别,两个语句生成的.NET MSIL字节码相同(启用和不启用编译器优化都一样):

enter image description here

如果y是一个类型名称(而不是值名称),则存在区别:第一个if语句无效并且不能编译,而if( x is not y )语句是一个类型模式匹配而不是一个常量模式匹配。

脚注:

  1. "Constant Pattern": "When the input value is not an open type, the constant expression is implicitly converted to the type of the matched expression".

  2. x is not null is more analogous to !(x == null) than x != null.

  3. C# 7.0 introduced some limited forms of constant-pattern matching, which was further expanded by C# 8.0, but it wasn't until C# 9.0 that the not negation operator (or is it a modifier?) was added.

  4. Given a non-constrained generic method, like so:

    void Foo<T>( T x )
    {
        if( x == null ) { DoSomething(); }
    
        DoSomethingElse();
    }
    

    ...when the JIT instantiates the above generic method (i.e.: monomorphization) when T is a value-type (struct) then the entire if( x == null ) { DoSomething(); } statement (and its block contents) will be removed by the JIT compiler ("elision"), this is because a value-tupe can never be equal to null. While you'd expect that to be handled by any optimizing compiler, I understand that the .NET JIT has specially hardcoded rules for that particular scenario.

    • Curiously in earlier versions of C# (e.g. 7.0) the elision rule only applied to the == and != operators, but not the is operator, so while if( x == null ) { DoSomething(); } would be elided, the statement if( x is null ) { DoSometing(); } would not, and in fact you would get a compiler error unless T was constrained to where T : class. Since C# 8.0 this seems to now be allowed for unconstrained generic types.
  5. Surprisingly I couldn't find an authoritative source on this (as the published C# specs are now significantly outdated; and I don't want to go through the csc source-code to find out either).

    • If neither the C# compiler or JIT do apply impossible-branch-elision in generic code with Constant-pattern expressions then I think it might simply because it's too hard to do at-present.
  6. Note that a constant-expression does not mean a literal-expression: you can use named const values, enum members, and so on, even non-trivial raw expressions provided all sub-expressions are also constant-expressions.

    • I'm curious if there's any cases where static readonly fields could be used though.
  7. Note that in the case of typeof(X) != y.GetType(), this expression will return true when X is derived from y's type (as they are different types), but x is not Y is actually false because x is Y (because x is an instance of a subclass of Y). When using Type it's better to do something like typeof(X).IsSubclassOf(y.GetType()), or the even looser y.GetType().IsAssignableFrom(typeof(X)).

    • Though in this case, as Char is a struct and so cannot participate in a type-hierarchy, so doing !x.IsSubclassOf(typeof(Char)) would just be silly.

2
在第8行中,单词“elison”的意思是什么? - dennis_vok
1
@dennis_vok 我猜他指的是这个 https://en.wikipedia.org/wiki/Copy_elision。编辑已排队。 - Etsitpab Nioliv
1
在这种情况下,它不是复制省略,而是编译器优化,以消除只有一个结果的比较。例如,如果 x 是非空的,则 x != null 总是可以优化为 true - cg909
2
感谢 @cg909 的澄清,这确实是一个小众术语,我已经用 https://ericlippert.com/2013/01/24/five-dollar-words-for-programmers-elision/ 替换了维基链接。 - Etsitpab Nioliv
1
屏幕截图中的软件是什么? - NotStanding with GoGotaHome
显示剩余3条评论

15

计算机使用如此愉悦的逻辑:两个不是数字的值不能相等。为什么生活中的更多事情不能像这样工作呢? - Neil Meyer
9
“爱因斯坦认为,自然界必须有简化的解释,因为上帝并不反复无常或任意妄为。然而,这种信仰并不能让软件工程师感到安慰。” ——弗雷德·布鲁克斯 - Davislor
Intel 8087确实有其他测试NaN的方法,例如FXAM指令。主要原因是像C这样的语言缺乏标准化的isNaN原语。 - nwellnhof
@nwellnhof,你的链接好像有问题,但感谢你的纠正。我会进一步研究并进行适当的编辑。 - Davislor

3

nul和undefined是变量可能包含的没有值的属性。相等性检查需要实际值才能确定是否相等。毕竟,如果没有人知道Sally和Peter拥有多少苹果,那么他们是否拥有相同数量的苹果的问题是没有意义的。

有时候你想检查一个变量是否有一个没有值的属性。基本的相等性检查是不足够的。这时候is / is not操作符就派上用场了。可以说!= 是一个值检查,而is / is not是一个属性检查。


1
虽然 null 在原则上类似于 NaN,但是在 null 引用上,is== 之间没有差异。null == null 的计算结果为 true。两个引用类型之间的模式匹配被指定为与 Equals(Object) 一致,并且“对 Equals(Object) 方法的调用等效于对 ReferenceEquals 方法的调用。” 这反过来又被指定为,“如果 objA 是与 objB 相同的实例或者两者都为 null,则返回 true。” 因此,null == nullnull is null 给出相同的结果。 - Davislor
也就是说,除非!=以出人意料的方式被重载,否则不会有任何差异。 - Davislor
顺便提一下,规范说明中提到,if (expr is Type v)被创建为显式null检查的简洁替代方式。因此,微软的编码风格更喜欢使用它而不是v != nullv is not null - Davislor
1
“你不能将值与NaN和NULL进行比较”的哲学是错误的。我说了。它对语言没有任何价值,还会恶化开发体验。我要反驳这一点,证明可以设计一种具有清晰、简单和逻辑一致的规则的语言,使用等号运算符允许与null进行有意义的比较(事实上:C#做得很好!Java、C++等也是如此),而ISO SQL则严格要求x = NULLUNKNOWN,这对于所有人,从初学者到专家都是一个重要的陷阱。在SQL中,“IS NULL”只是一个狗戏表演。 - Dai
NaN和null是变量可以包含没有值的属性。这在一般意义上是正确的,特别是在学术背景下讨论编程语言设计时,尤其是涉及范畴论。然而,在C#中具体来说是不正确的:在C#/.NET中,NaN仅适用于IEEE-754浮点数(而不适用于其他数字类型),而null本身就是一个值,而不是被概念化为“没有值”的情况,因为在C#中,null是引用的值,也适用于Nullable<T>(也不要称其为单子)。 - Dai
显示剩余3条评论

0

TLDR: x != 1 && x != 2 && x != 3 等同于 x 不是 1 且不是 2 且不是 3 或者 x 不是 (1 或 2 或 3)

x 不是 1 或 2 或 3 等同于 x != 1 || x == 2 || x == 3


也许有点晚来参加派对,但例子是为了澄清问题。
看看 @Dai 的常见例子表。 x != 1 && x != 2 && x != 3x is not 1 or 2 or 3 应该是等价的(如果我理解正确的话)。
但它们并不相同。
问题在于运算符的优先级。
引用: 模式组合器的优先级从高到低如下: - not - and - or x != 1 && x != 2 && x != 3 等同于 x is not 1 and not 2 and not 3

x != 1 && x != 2 && x != 3 等同于 x 不是 (1 或 2 或 3)

using System;
                    
public class Program
{
    public static void Main()
    {
        var x = 3;
        if(x is not 1 or 2 or 3){            
            Console.WriteLine("x is not 1 and is not 2 and is not 3");
        }       
    }
}

output: "x is not 1 and is not 2 and is not 3"

        if(x != 1 && x != 2 && x != 3){
            Console.WriteLine("x is not 1 and is not 2 and is not 3");
        }
        else{
            Console.WriteLine("x is 1 or 2 or 3");
        }   

output: "x is 1 or 2 or 3"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接