103

Is this:

if(x != y)
{

}

different from this:

if (x is not y)
{

}

Or are there no differences between the two conditions?

Thanos Kyprianos
  • 1,628
  • 2
  • 7
  • 13
  • Does c# have a `not` keyword? I thought the second example would be written `if (!(x is y))`. – John Wu Nov 17 '21 at 20:24
  • Is or is not would be used to compare type, but == or != would compare the variables’ values – Cade Weiskopf Nov 17 '21 at 20:25
  • 3
    @JohnWu: Yes, as of C# 9. Along with `or` and `and` keywords. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#logical-patterns – StriplingWarrior Nov 17 '21 at 20:25
  • 40
    @JohnWu C# sat too close to VB at a christmas do nearly 2 years ago; ended up catching something called C#ViB-19 - it's never been the same since – Caius Jard Nov 17 '21 at 20:28
  • 17
    @CaiusJard I know it seems silly at first-glance, but now I love it: being able to do `x is 1 or 2 or 3` is a LOT nicer than doing `( x == 1 || x == 2 || x == 3 )` *and* as a bonus: when `x` is an expression instead of a value then the `is` operator only evaluates `x` _once_, whereas `( x == 1 || x == 2 || x == 3 )` will causes 3 evaluations of `x`. – Dai Nov 17 '21 at 20:39
  • 1
    @Dai oh the evaluation part sounds really nice! – Thanos Kyprianos Nov 17 '21 at 20:41
  • 3
    @Dai - I really don't know much (if anything) about compilers, but in a case as simple as `( x == 1 || x == 2 || x ==3 )` wouldn't the compiler be able to optimize that down to a single evaluation and then a comparison in the same way as `x is 1 or 2 or 3`? I'm honestly not doubting you for a second - I'm just asking for my own education. – Spratty Nov 18 '21 at 09:51
  • 3
    @Spratty For simple fields or local variables there is generally no difference, but for other types of expressions (for example, computed properties) it can't always safely optimize the first case down to a single evaluation because evaluating `x` might have side effects and the compiler has no way of knowing if the code intended for the expression to be reevaluated after each comparison. – castholm Nov 18 '21 at 15:45
  • 2
    Waiting for C# and COBOL to converge.... IF X IS EQUAL TO 2 THEN DISPLAY 'YES' END-IF. – shoover Nov 18 '21 at 16:23
  • 1
    @shoover: Haven't C# and COBOL already converged into NetCOBOL? – dan04 Nov 19 '21 at 01:11
  • @Toddleson: The question is using `is not`, that isn't just `is` composed with `not`. – Ben Voigt Nov 19 '21 at 20:36

3 Answers3

158

Comparison table:

Operator != is not
Original purpose Value inequality Negated pattern matching
Can perform value inequality Yes Yes
Can perform negated pattern matching No Yes
Can invoke implicit operator on left-hand operand Yes No
Can invoke implicit operator on right-hand operand(s) Yes Yes1
Is its own operator Yes No2
Overloadable Yes No
Since C# 1.0 C# 9.03
Value-type null-comparison branch elision4 Yes No[Citation needed]5
Impossible comparisons Error Warning
Left operand Any expression Any expression
Right operand(s) Any expression Only constant expressions6
Syntax <any-expr> != <any-expr> <any-expr> is [not] <const-expr> [or|and <const-expr>]*
and more

Common examples:

Example != is not
Not null x != null x is not null
Value inequality example x != 'a' x is not 'a'
Runtime type (mis)match x.GetType() != typeof(Char) x is not Char7
SQL x NOT IN ( 1, 2, 3 ) x != 1 && x != 2 && x != 3 x is not 1 or 2 or 3

To answer the OP's question directly and specifically:

if( x != y ) { }
// vs:
if( x is not y ) { }
  • If x is an integral value-type (e.g. int/ Int32) and y is a const-expression (e.g. const int y = 123;) then no, there is no difference, and both statements result in the same .NET MSIL bytecode being generated (both with and without compiler optimizations enabled):

    enter image description here

  • If y is a type-name (instead of a value name) then there is a difference: the first if statement is invalid and won't compile, and the if( x is not y ) statement is a type pattern match instead of a constant pattern match.


Footnotes:

  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.
Dai
  • 141,631
  • 28
  • 261
  • 374
15

An additional difference to the ones listed in the excellent accepted answer is that (since C# 7.0), is between two NaN values is a pattern that matches, because x.Equals(y) is true when both x and y are NaN, and a NaN value does not have an integral type. Therefore, is not between two NaN values returns that the pattern is not a match.

However, C# follows IEEE floating-point and C by specifying that a != comparison between two NaN values is true and an == comparison between them is false. This was mainly because the Intel 8087 floating-point co-processor back in 1980 had no other way to test for NaN.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • Two values that are not numbers cannot be equall after all. Computers sure use such a pleasant logic. Why can more things in life not work like that? – Neil Meyer Nov 18 '21 at 10:20
  • 9
    @NeilMeyer “Einstein argued that there must be simplified explanations of nature, because God is not capricious or arbitrary. No such faith comforts the software engineer.” —Fred Brooks – Davislor Nov 18 '21 at 10:28
  • The Intel 8087 did have other ways to test for NaN, for example the [FXAM instruction](https://www.felixcloutier.com/x86/fxam). The main reason was that languages like C were lacking a standardized isNaN primitive. – nwellnhof Nov 19 '21 at 16:15
  • @nwellnhof Your link seems to be broken, but thanks for the correction. I’ll look into that some more and edit as appropriate. – Davislor Nov 19 '21 at 18:59
3

Nan and null are properties that variables can contain that have no values. Equality checks require an actual value to determine equality. After all the question on whether Sally and Peter has the same amount of apples when nobody knows how many Apples either of them has is meaningless.

Sometimes you want to check if a variable has a property without a value. A basic equality check would not be sufficient for this. That is when is / is not operator is useful. It could be said != is a value check where is / is not a property check.

Neil Meyer
  • 473
  • 4
  • 15
  • 1
    While `null` is similar in principle to NaN, there is no discrepancy between `is` and `==` on `null` references. `null == null` evaluates to true. A pattern match between two reference types is specified as consistent with `Equals(Object)`, and “a call to the `Equals(Object)` method is equivalent to a call to the `ReferenceEquals` method.” This in turn is specified as, “`true` if `objA` is the same instance as `objB` or if both are `null`.” Therefore, `null == null` and `null is null` give the same result. – Davislor Nov 18 '21 at 18:03
  • That is, there is no discrepancy unless `!=` has been overloaded in a surprising way. – Davislor Nov 18 '21 at 18:13
  • By the way, [the spec says](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-7.0/pattern-matching) that `if (expr is Type v)` was created as a concise replacement for explicit `null` checks. So, MS’ house style favors that over either `v != null` or `v is not null`. – Davislor Nov 18 '21 at 18:18
  • And the downvote did not come from me, by the way. – Davislor Nov 18 '21 at 18:46
  • 1
    The "You cannot compare values to NaN and NULL" philosophy is _wrong_. There, I said it. It adds no value to a language and worsens the dev-ex. I'll counter that _it is demonstrably possible_ to design a language with clear, straightforward, and logically consistent rules that allow meaningful comparisons to nulls using equality operators (indeed: C# does it just fine! As does Java, and C++, and more), whereas ISO SQL is rigidly strict about `x = NULL` being `UNKNOWN` and it's a significant _gotcha_ to everyone, from beginners to even experts. `IS NULL` in SQL is just a dogshow hoop. – Dai Nov 19 '21 at 11:25
  • "Nan and null are properties that variables can contain that have no values." this is true in a general-sense, when discussing programming-language design in an academic context, especially w.r.t. category-theory - however in C# specifically that is incorrect: in C#/.NET `NaN` only applies to IEEE-754 floats (and no other numeric types), and _`null` is a value in itself_ instead of being conceptualized as _the absence of a value_, as in C# `null` _is_ the value-of-a-reference) - this also applies to `Nullable` (don't call it a monad either). – Dai Nov 19 '21 at 11:33
  • @Dai: You've obviously thought about this (+1). In support: while one can formulate logically consistent rules for comparisons involving `null`, things fall apart unavoidably around `NaN`. Reasoning that `NaN /*Not a Number*/ != NaN` makes perfect sense until `x != x /*something != itself*/`. My own philosophising stopped at the realisation that `null` (even as value of a reference) represents *affirmation* of a legitimate - empty placeholder - state, whereas NaN represents *contradiction* - an illegitimate state where a placeholder for things of a type contains a thing not of that type. – AlanK Nov 24 '21 at 05:19
  • @AlanK You're not wrong, but a better solution to dealing with `NaN` is defining `NaN` as a _type_ instead of a value (this does require language support for _refinement types_, of course - so us C#/.NET users will need to wait another decade or two...). So all operations on IEEE floats will be redefined as returning `value` or `value | NaN` or `NaN` based on operands and refinements of those operands, and `==` simply wouldn't be defined for the `NaN` type, giving you a compile-time error for `NaN == NaN` - which is much better than a _surprise_ runtime `false` expression result. – Dai Nov 24 '21 at 06:38