<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):
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:
"Constant Pattern": "When the input value is not an open type, the constant expression is implicitly converted to the type of the matched expression".
x is not null is more analogous to !(x == null) than x != null.
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.
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.
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.
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.
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 xtrue3 Y (because x is an instance of a subclass of Y). When using true0 it's better to do something like true1, or the even looser true2.
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.
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 ABC1 is ABC2 when both ABC3 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.
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.