DEV Community

Cover image for When does != true != !true?
Jason Elkin
Jason Elkin

Posted on

When does != true != !true?

This is a rather contrived tongue-in-cheek edge case, and super nerdy... but I have some code that does this:

Console.WriteLine(liar == true); // True
Console.WriteLine(liar != true); // True
Console.WriteLine(!liar); // False
Enter fullscreen mode Exit fullscreen mode

WHY!?!

Partly because I'm a contrarian, but also to highlight something that we might take for granted in C#.

@leekelleher wrote a great article about his take on coding standards where he compares two different ways of doing the same thing, specifically this:

if(!string.IsNullOrWhiteSpace(input)) { 
  //... 
}

// or

if(string.IsNullOrWhiteSpace(input) == false) { 
  //... 
}
Enter fullscreen mode Exit fullscreen mode

Lee points out that functionally (and even in IL) these two lines of code are exactly the same (and for string.IsNullOrWhiteSpace() that will almost certainly always be the case), but they don't have to be the same...

Logically these lines of code are doing something different, which shouldn't be a surprise because they are different.

If we do away with the method being invoked, which returns a bool, and the if then we can focus a bit on the logic.


var x = !a; // x IS NOT a.

var x = a == false; // x IS (a EQUALS false).

Enter fullscreen mode Exit fullscreen mode

That first line is a logical negation. The second line is an equality comparison against a (static) false value. These are using different operators, and in C# operators are implemented explicitly and independently and can be overridden.

What do I mean by that? let's take a look at some code from my (very naughty) Liar struct.

Jason's Evil Liar Struct

BTW, don't do this πŸ™ˆ.

public struct Liar(bool isTrue)
{
    private readonly bool _true = isTrue;

    public static bool operator ==(Liar left, bool right)
    {
        return left._true == right;
    }

    public static bool operator !=(Liar left, bool right)
    {
        return left._true == right; // 😈
    }
}
Enter fullscreen mode Exit fullscreen mode

Here I'm defining what should happen when someone compares my Liar struct to a bool. You can, of course, spot the deliberate mistake.

The point is, != and == invoke different methods, i.e. != is not simply a logical negation of ==. This means that a != b is not doing the same as !(a == b) and vice versa.

So what about the logical negation operator? You'd be forgiven for thinking that a has to be a bool for !a to work, but that's not the case. It could be anything that has the implicit operator for a bool defined (or the true/false operators). That's right JavaScript fans, you can make truthy/falsey types in C# 😬.

Here's the code for implicit operator that lets me use my Liar struct as if it's a bool:

public static implicit operator bool(Liar liar)
{
  return liar._true;
}
Enter fullscreen mode Exit fullscreen mode

Now we can do this:

Liar liar = new(true);

if (liar)
{
  //
}
Enter fullscreen mode Exit fullscreen mode

We can also add an implicit operator so that we don't even need to create an instance of Liar, and can just assign true to it:

public static implicit operator Liar(bool isTrue)
{
  return new(isTrue);
}
Enter fullscreen mode Exit fullscreen mode

Now we can do this:

Liar liar = true;
Enter fullscreen mode Exit fullscreen mode

Yeah, but this never happens in the real world

Unless someone does something silly... and people never do silly things...

Though that's not really my point, there are a few other things I'd like you to take away from this:

1. Code that looks like it's doing something different, is doing something different

Two lines of code that look like they're doing something different almost always are doing something different, even if the outcome is functionally the same.

Sure, in the real world the difference between !string.IsNullOrWhiteSpace(input) and string.IsNullOrWhiteSpace(input) == false is largely academic, so you can choose what works for you. But that's not necessarily going to be the case all the time, and being aware of the difference here might just save you a few hours of debugging one day.

2. In C# we can choose how operators work

At least with our own code, it's called operator overloading. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/operator-overloading

I've known some C# devs get confused by equality in JavaScript with ==, ===, !=,!== and truthy/falsely logic like !someString. In many ways this is actually a lot simpler than C# because it is what it is, and we can't change it.

In C# all bets are off - you have complete control in defining how operators work with the classes/structs etc. that you define.

This is actually a super powerful feature of C# and I love it when library authors implement it to improve the developer experience, like this example:

var ratio = new(16,9);
if(ratio == "16:9")) //πŸ˜™πŸ‘Œ
Enter fullscreen mode Exit fullscreen mode

3. You can write operators to compare pretty much anything!

My Liar is just a wrapped bool to make a silly example, but we can have any logic inside these operators and use them for more complicated types.

There's also no limit on what types you can add operators for, here I've just added code to compare Liar with bool but we can write code to compare our types to anything.

Thanks for nerding out with me...

If you've not had a go at overloading operators in your own types before, why not give it a try?

I'm sure you've got classes that would be useful if they could be compared against a string value, like the ratio example above, or an int or whatever.


Here's the full example Liar struct

public readonly struct Liar(bool isTrue) : IEquatable<bool>
{
    private readonly bool _true = isTrue;

    public static bool operator ==(Liar left, bool right)
    {
        return left._true == right;
    }

    public static bool operator !=(Liar left, bool right)
    {
        return left._true == right; // 😈
    }

    public static implicit operator bool(Liar liar)
    {
        return liar._true;
    }

    public static implicit operator Liar(bool isTrue)
    {
        return new(isTrue);
    }

    public override bool Equals(object obj)
    {
        return obj is Liar liar && liar._true == _true;
    }

    public override int GetHashCode()
    {
        return _true.GetHashCode();
    }

    public bool Equals(bool other)
    {
        return _true == other;
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)