Is this:
if(x != y)
{
}
different from this:
if (x is not y)
{
}
Or are there no differences between the two conditions?
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 |
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 Char 7 |
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.
==
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).
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.
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 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))
.
Char
is a struct and so cannot participate in a type-hierarchy, so doing !x.IsSubclassOf(typeof(Char))
would just be silly.