16

I have a custom class with implement both the == and the implicit for boolean operator.

Is this the correct way to handle all possible, if ==/!= statements and get the expected result? Like this:

public class Foo
{
    public bool Result { get; set; }

    public static bool operator ==(bool @bool, Foo foo)
    {
        return Equals(foo, @bool);
    }
    public static bool operator !=(bool @bool, Foo foo)
    {
        return NotEquals(foo, @bool);
    }
    public static bool operator ==(Foo foo, bool @bool)
    {
        return Equals(foo, @bool);
    }
    public static bool operator !=(Foo foo, bool @bool)
    {
        return NotEquals(foo, @bool);
    }
    public static bool operator ==(Foo foo, Foo fooB)
    {
        return Equals(foo, fooB);
    }
    public static bool operator !=(Foo foo, Foo fooB)
    {
        return NotEquals(foo, fooB);
    }
    public static implicit operator bool(Foo foo)
    {
        try { return foo.Result; }
        catch { return false; }
    }

    private static bool Equals(Foo foo, Foo fooB)
    {
        if (object.Equals(foo, null))
        {
            if (object.Equals(fooB, null))
                return true;

            return false;
        }

        if (object.Equals(fooB, null))
            return false;

        return foo.Result == fooB.Result;
    }
    private static bool NotEquals(Foo foo, Foo fooB)
    {
        if (object.Equals(foo, null))
        {
            if (object.Equals(fooB, null))
                return false;

            return true;
        }

        if (object.Equals(fooB, null))
            return true;

        return fooB.Result != foo.Result;
    }
    private static bool Equals(Foo foo, bool @bool)
    {
        if (object.Equals(foo, null))
            return true;

        return @bool == foo.Result;
    }
    private static bool NotEquals(Foo foo, bool @bool)
    {
        if (object.Equals(foo, null))
            return false;

        return @bool != foo.Result;
    }

}

I am especially wondering about the fact that its seems you really need to implement overloads for either

if (new Foo() != true)

and

if (true != new Foo())
Rand Random
  • 7,300
  • 10
  • 40
  • 88
  • Why would you do this instead of having a method `bool PossibleResult()`? – Jon Jan 31 '14 at 16:59
  • 1
    @Jon I am developing a EntityFramework App, with Firebird as my DataBase sadly the current version of Firebird doesnt support boolean nativly so I want to create a custom class assign this to the fields in my EDMX file. With the implicit conversion to bool and the overriding of equality operators I can then use DataBinding as if it would be a boolean and dont need to write wrappers for every bool property. – Rand Random Jan 31 '14 at 18:19
  • I 'm not at all sure that I have the whole picture, but it sounds like build time code generation could help you. Google T4 templates, they are built into VS and fit the bill exactly. – Jon Jan 31 '14 at 20:39
  • It's not really clear what you're asking - or whether this would actually be better on CodeReview. I really don't like your implicit conversion to bool implementation though... catching *all* exceptions and swallowing them, instead of just checking for null? Ick. – Jon Skeet Mar 06 '14 at 16:08
  • And are you *sure* that the null handling is what you want? `Foo f = null; if (f == true)`...? – Jon Skeet Mar 06 '14 at 16:10
  • @JonSkeet Yeah, it would have been better on code review.. sry about that. The only Q I had with this code, if I need to write overloads for (bool b, Foo f) and with Foo at the fist position (Foo f, bool b), seemed kinda strange to me that this seems to be a requirement. About your question with `Foo f = null; if (f == true)`, yes thats exactly like I desire it. This class is used for return values, it replaces the TryXXX(out YYY) pattern, since one of my co-workers is against this pattern and wanted 1 return value. So we have `Foo` with a property Result of `T`, which would be the out. – Rand Random Mar 10 '14 at 12:37
  • @RandRandom: I'm fine with getting rid of the `TryXXX` pattern, but that doesn't mean you need to have implicit conversions. Have a look at how we do it in Noda Time, for example: http://nodatime.org/1.2.x/api/html/T_NodaTime_Text_ParseResult_1.htm – Jon Skeet Mar 10 '14 at 12:49
  • @JonSkeet - Thought about this as well, thats the reason why the property `Result` in the code is public in the first place, but I was confronted with the Q. "So instead of having to write out xxx everytime, I have to type .Result every freakin' time?" And I was like with a smile on the face "Sure why not? You dont really ask for a implicit conversion to bool or are you?" and than he "Now we are talking buisness.", so I was kinda forced to do this. Since I have never done a implicit conversion to bool I was wondering if everything is correct. Isnt a topic you find on the internet easily. – Rand Random Mar 10 '14 at 14:39
  • 1
    Well, there's "correct" as in "doing what you intend" and then there's "pleasant code I'd like to work with." I personally try to avoid code which tries to be too "clever" like this whenever possible. This feels like a type which is far too surprising to be to my taste. – Jon Skeet Mar 10 '14 at 15:42
  • Slightly off-topic but I'm curious: Why must your bools be called `bool` w/ `@` syntax? Couldn't you have just named them anything else? – Dan Bechard Mar 10 '14 at 20:18
  • 1
    @Dan I couldn't think of a good name so I called it bool, and you have to apply the @ since bool is a reserved keyword in C#. – Rand Random Mar 11 '14 at 09:02

2 Answers2

24

I think you've written too much code :-)

The following is sufficient:

public class Foo
{
    public bool Result { get; set; }

    public static implicit operator bool(Foo foo)
    {
        return !object.ReferenceEquals(foo, null) && foo.Result;
    }
}

The compiler will then know how to implicitly convert variables of type Foo into bool. (And null will be converted to false).

So, when you write:

new Foo() == false

The compiler will use the implicit type converter to get a bool value from Foo and then use the standard equality operator for bool.

If we look at the IL that the compiler generates for that expression we find:

newobj instance void FooBool.Foo::.ctor()               // new Foo()
call bool FooBool.Foo::op_Implicit(class FooBool.Foo)   // implicit operator (Foo => bool)
ldc.i4.0                                                // false
ceq                                                     // equality operator (bool)

Here's a test:

static void Main(string[] args)
{
    AssertTrue(new Foo() == false);
    AssertTrue(false == new Foo());
    AssertFalse(new Foo() != false);
    AssertFalse(false != new Foo());
    AssertTrue(new Foo { Result = true } == true);
    AssertTrue(true == new Foo { Result = true });
    AssertFalse(new Foo { Result = true } != true);
    AssertFalse(true != new Foo { Result = true });
}

static void AssertTrue(bool value)
{
    Console.WriteLine(value ? "ok" : "not ok");
}

static void AssertFalse(bool value)
{
    Console.WriteLine(value ? "not ok" : "ok");
}

It prints ok for each test. So this simplified code should fulfill your needs if I understood them correctly.

UPDATE

To allow the equality operator to work for instances of Foo (which may be null):

public static bool operator ==(Foo a, Foo b)
{
    if (object.ReferenceEquals(a, b))
    {
        return true;
    }
    else if (object.ReferenceEquals(a, null))
    {
        return !b.Result;
    }
    else if (object.ReferenceEquals(b, null))
    {
        return !a.Result;
    }
    else
    {
        return a.Result == b.Result;
    }
}

You should then also implement the inequality operator:

public static bool operator !=(Foo a, Foo b)
{
    return !(a == b);
}

And also override GetHashCode + Equals

public override int GetHashCode()
{
    return this.Result ? 1 : 0;
}

public override bool Equals(object obj)
{
    if (object.ReferenceEquals(obj, null))
    {
        return !this.Result;
    }

    Type t = obj.GetType();

    if (t == typeof(Foo))
    {
        return this.Result == ((Foo)obj).Result;
    }
    else if (t == typeof(bool))
    {
        return this.Result == (bool)obj;
    }
    else
    {
        return false;
    }
}
Mårten Wikström
  • 11,074
  • 5
  • 47
  • 87
  • I think, that this code wouldnt work 100% when comparing 2 Foos or? Or would the IL implicit convert both side? `new Foo() { Result = false) == new Foo() { Result = false }` this would return false in your code wouldnt it, so I believe that samy's answer is more complete or? – Rand Random Mar 13 '14 at 10:22
  • Sorry, didn't understand that you wanted the equality operator to work for Foo instances. I've updated my answer. – Mårten Wikström Mar 13 '14 at 10:52
  • @MårtenWikström I don't understand why your == operator for Foo and Foo returns the inverse of the other Foo result when one is null; if FooA = null and FooB = Foo(true) then comparing both will return false, but if FooA = null and FooB = Foo(false) it will return true, which is incorrect? What is the reasoning for this behavior? – samy Mar 13 '14 at 11:49
  • If A is null it should be equal to B if B is either null or has a false result. It is indeed correct to return true when FooA=null and FooB=Foo(false) because you have defined that a null Foo instance shall be implicitly converted into a false boolean. – Mårten Wikström Mar 13 '14 at 12:13
1

I think that you explicitly covered all the bases in your code; if needed you must consider the ordering of parameters for the operator so if you want your Equals function to be called for both ordering of parameters what you did is right.

However it looks a bit overkill in the case of comparing Foo to bool since you could simply rely on the implicit conversion. This would allow you to remove all operators between the two types as well as the Equals and NotEquals methods.

What's more, it would avoid some inconsistency in your code regarding the conversion of a null Foo to boolean. When you pass a null Foo to the Equals method it will return true whereas in the implicit conversion a null Foo will return false:

true == (Foo)null; //true
true == Convert.ToBoolean((Foo)null); //false

In closing, here is how i'd write the Foo class, i think it is sufficient:

public class Foo
{
    public bool Result { get; set; }

    public static bool operator ==(Foo foo, Foo fooB)
    {
        return Equals(foo, fooB);
    }
    public static bool operator !=(Foo foo, Foo fooB)
    {
        return NotEquals(foo, fooB);
    }
    public static implicit operator bool(Foo foo)
    {
        return foo == null ? false : foo.Result;
    }

    private static bool Equals(Foo foo, Foo fooB)
    {
        if (object.Equals(foo, null))
        {
            return object.Equals(fooB, null);
        }

        if (object.Equals(fooB, null))
            return false;

        return foo.Result == fooB.Result;
    }
    private static bool NotEquals(Foo foo, Foo fooB)
    {
        return !Equals(foo, fooB);
    }
}
samy
  • 14,832
  • 2
  • 54
  • 82
  • I would highly recommend not using a try-catch block in your static implicit operator bool block. Just check if Foo is null, if it is return false, otherwise return foo.Result – Moop Mar 12 '14 at 17:15