Is there a difference between !(a == b) and a != b
Sure there's a difference. If !
and ==
and !=
are overloaded, then the first calls the first two operators, and the second calls the third. Those are permitted to do very different things, though it would be foolish to do so.
In fact it is common to implement overloaded ==
and !=
operators in terms of each other; you might say bool operator !=(C x, C y) => !(x == y);
for example. In that case, x != y
would be an infinite recursion, which is plainly different than calling !(x == y)
!
In most cases, they're the same - but they don't have to be. !=
and ==
can be overloaded separately, with different logic. Here's an example:
using System;
class Test
{
// All this code is awful. PURELY FOR DEMONSTRATION PURPOSES.
public static bool operator==(Test lhs, Test rhs) => true;
public static bool operator!=(Test lhs, Test rhs) => true;
public override bool Equals(object other) => true;
public override int GetHashCode() => 0;
static void Main()
{
Test a = null;
Test b = null;
Console.WriteLine(a != b); // True
Console.WriteLine(!(a == b)); // False
}
}
In the vast majority of cases, a != b
and !(a == b)
will have exactly the same behavior, and a != b
is almost always clearer. But it's worth being aware that they can differ.
It can get even more pathological - a != b
and !(a == b)
may even have different types. For example:
using System;
class Test
{
// All this code is awful. PURELY FOR DEMONSTRATION PURPOSES.
public static Test operator==(Test lhs, Test rhs) => new Test();
public static Test operator!=(Test lhs, Test rhs) => new Test();
public static string operator!(Test lhs) => "Negated";
public override string ToString() => "Not negated";
public override bool Equals(object other) => true;
public override int GetHashCode() => 0;
static void Main()
{
Test a = null;
Test b = null;
Console.WriteLine(a != b); // "Not negated"
Console.WriteLine(!(a == b)); // "Negated"
}
}
Here a != b
is of type Test
, but !(a == b)
is of type string
. Yes, this is horrible and you're unlikely to run into it in real life - but it's the kind of thing a C# compiler needs to know about.