问题 隐式(bool)和==运算符覆盖 - 正确处理if语句


我有一个自定义类,同时实现了 == 和 implicit 对于布尔运算符。

这是处理所有可能的if == /!=语句并获得预期结果的正确方法吗? 喜欢这个:

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;
    }

}

我特别想知道它似乎真的需要实现重载

if (new Foo() != true)

if (true != new Foo())

12080
2018-01-31 16:55


起源

你为什么要这样做,而不是有一个方法 bool PossibleResult()? - Jon
@Jon我正在开发一个EntityFramework应用程序,Firebird作为我的DataBase,遗憾的是Firebird的当前版本不支持boolean nativly所以我想创建一个自定义类将它分配给我的EDMX文件中的字段。通过隐式转换为bool和覆盖等于运算符,我可以使用DataBinding,就好像它是一个布尔值,并且不需要为每个bool属性编写包装器。 - Rand Random
我完全不确定是否有全局,但听起来构建时代码生成可以帮助你。谷歌T4模板,它们内置于VS并完全符合要求。 - Jon
你不是很清楚你在问什么 - 或者这在CodeReview上是否会更好。我真的不喜欢你隐式转换为bool实现虽然...捕获 所有 异常并吞下它们,而不仅仅是检查null?伊克。 - Jon Skeet
@Dan我想不出一个好名字所以我称之为bool,你必须应用@,因为bool是C#中的保留关键字。 - Rand Random


答案:


我觉得你写的代码太多了:-)

以下就足够了:

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

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

然后编译器将知道如何隐式转换类型的变量 Foo 成 bool。 (和 null 将被转换为 false)。

所以,当你写:

new Foo() == false

编译器将使用隐式类型转换器来获取 bool 来自的价值 Foo 然后使用标准的相等运算符 bool

如果我们查看编译器为该表达式生成的IL,我们会发现:

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)

这是一个测试:

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");
}

它打印 ok 每次测试。因此,如果我理解正确,这个简化的代码应该满足您的需求。

UPDATE

允许相等运算符用于实例 Foo (可能为空):

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;
    }
}

您还应该实现不等式运算符:

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

并覆盖 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;
    }
}

12
2018-03-12 16:53



我想,在比较2个Foos时,这个代码不能100%工作?或者IL隐含转换双方? new Foo() { Result = false) == new Foo() { Result = false } 这会在你的代码中返回false,所以我相信samy的答案更完整或者? - Rand Random
抱歉,您不明白您希望等于运算符适用于Foo实例。我已经更新了我的答案。 - Mårten Wikström
@MårtenWikström我不明白为什么你的foo和Foo的==运算符返回另一个Foo结果的倒数,当一个为空时;如果FooA = null且FooB = Foo(true),则比较两者将返回false,但如果FooA = null且FooB = Foo(false),则返回true,这是不正确的?这种行为的原因是什么? - samy
如果A为null,则如果B为null或具有错误结果,则它应等于B.当FooA = null且FooB = Foo(false)时返回true确实是正确的,因为您已经定义了一个null Foo实例应该被隐式转换为false布尔值。 - Mårten Wikström


我认为您明确涵盖了代码中的所有基础;如果需要,你必须考虑操作员的参数顺序,如果你想要你的 Equals 你所做的参数排序调用的函数是正确的。

然而,在比较的情况下,它看起来有点矫枉过正 Foo 至 bool 因为你可以简单地依赖隐式转换。这将允许您删除两种类型之间的所有运算符以及 Equals 和 NotEquals 方法。

更重要的是,它可以避免代码中关于将null Foo转换为布尔值的一些不一致。当你传递一个空Foo到 Equals 它将返回的方法 true 而在隐式转换中,将返回null Foo false

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

最后,这是我如何编写Foo类,我认为这已经足够了:

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);
    }
}

1
2018-03-12 11:21



谢谢你的回答,真的很感激。 - Rand Random
我强烈建议不要在静态隐式运算符bool块中使用try-catch块。只检查Foo是否为null,如果返回false,否则返回foo.Result - Moop
的确,编辑。我完全忽略了这个:) - samy