Search code examples
c#if-statementsyntax

Is there a difference between "!=" and "is not" in C#?


Is this:

if(x != y)
{

}

different from this:

if (x is not y)
{

}

Or are there no differences between the two conditions?


Solution

  • 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.