过载的false运算符何时执行并且它有什么作用?

10

我一直在寻找实际运行的代码,其中重载了 false 运算符。

这个问题 (C# 中 false 运算符有什么作用?) 有些相似,但是被接受的回答链接到了返回404错误的url。我也看过如何操作 true 和 false 的重载以及其他一些问题。

我在几乎所有答案中发现,false 只有在使用短路且运算符时才会执行,例如 x && y。这被计算为 T.false(x) ? x : T.&(x, y)

好的,那么我有以下代码。这个 struct 包含了一个 int,如果这个整数大于零则认为它是真的。

public struct MyStruct {
    private int _i;

    public MyStruct(int i) {
        _i = i;
    }

    public static bool operator true(MyStruct ms) {
        return ms._i > 0;
    }

    public static bool operator false(MyStruct ms) {
        return ms._i <= 0;
    }

    public override string ToString() {
        return this._i.ToString();
    }
}

现在我希望以下程序可以执行并使用重载的 false 运算符。

class Program {
    private static void Main() {
        MyStruct b1 = new MyStruct(1); // to be considered true
        MyStruct b2 = new MyStruct(-1); // to be considered false

        Console.WriteLine(b1 && b2);
        Console.WriteLine(b2 && b1);
    }
}

然而,它甚至不能编译。它说无法对类型为'MyStruct'和'MyStruct'的操作数应用运算符“&&”。
我知道我可以实现一个&运算符的重载。因此,让我们这样做。&必须返回MyStruct,所以我不能使它返回bool。
public static MyStruct operator &(MyStruct lhs, MyStruct rhs) {
    return new MyStruct(lhs._i & rhs._i);
}

现在代码已经编译通过。其输出为1-1。因此,b1 && b2的结果与b2 && b1不同。
如果我调试代码,会发现b1 && b2先在b1上执行false运算符,返回false。然后它对b1和b2执行&运算符,在1和-1上执行按位与操作,得到1。所以它确实首先检查b1是否为false。
第二个表达式b2 && b1先在b2上执行false运算符,返回true。结合我使用短路的事实,它不处理b1,只打印出b2的值。
因此,是的,在使用短路时会执行false运算符。但它不会在第二个参数上执行truefalse运算符,而是在操作数上执行重载的&运算符。
什么情况下可以用到这个技巧呢?或者说,如何让我的类型能够检查两个变量是否都为true?

你尝试过对 && 运算符进行重载吗? - Snowbear
@sSnowbear:你是什么意思?你不能重载 && 或 || 运算符。请参阅 http://msdn.microsoft.com/en-us/library/8edha89s(v=VS.100).aspx - comecme
@Snowbear - 那正是原始文章作者试图实现的功能,尽管微软不允许重载 && 和 || 运算符。 - IAmTimCorey
5个回答

5
您提到的URL不存在,可以在此处找到其内容:

http://web.archive.org/web/20080613013350/http://www.ayende.com/Blog/archive/2006/08/04/7381.aspx

作者所提到的文章在这里:

http://web.archive.org/web/20081120013852/http://steve.emxsoftware.com/NET/Overloading+the++and++operators

为了避免再次出现相同的问题,以下是文章的要点:
几个月前,我发布了有关我们查询API的帖子,并解释了它的工作原理。我们的查询API允许我们使用强类型的C#语法来表达查询:
List<Customer> customers = repository.FindAll(Customer.Columns.Age == 20 & Customer.Columns.Name == “foo”);

在我之前的帖子中,我指出不能直接重载&&和||运算符,因为框架不允许这样的疯狂操作...至少不是直接的。特别地,无法重载成员访问、方法调用或=、&&、||、?:、checked、unchecked、new、typeof、as和is运算符。在过去的一个月里,我对这个问题进行了一些调查,看看是否可以以我想要的方式使&&和||行为,今晚我找到了MSDN上的条件逻辑运算符页面,给了我我想要的答案:操作x&&y被评估为T.false(x)?x:T.&(x,y),其中T.false(x)是在T中声明的false运算符的调用,而T.&(x,y)是所选运算符&的调用。换句话说,首先评估x,并在结果上调用false运算符以确定x是否绝对为false。然后,如果x绝对为false,则操作的结果是先前计算的x的值。否则,将评估y,并在先前计算的x的值和计算的y的值上调用所选的&运算符以产生操作的结果。操作x || y被评估为T.true(x)?x:T.|(x,y),其中T.true(x)是在T中声明的true运算符的调用,而T.|(x,y)是所选运算符|的调用。换句话说,首先评估x,并在结果上调用true运算符以确定x是否绝对为true。然后,如果x绝对为true,则操作的结果是先前计算的x的值。否则,将评估y,并在先前计算的x的值和计算的y的值上调用所选的|运算符以产生操作的结果。由于我们已经有了&和|运算符,所以只需重载真和假运算符都返回false即可。这导致&和|运算符总是被调用,从而将两个条件对象转换为AndCriteria/OrCriteria!因此,现在我们可以使用我们习惯的&&和||语法来表达我们的条件表达式。
repository.FindAll(Customer.Columns.Age == 20 && Customer.Columns.Name == “foo”);

repository.FindAll(Customer.Columns.FirstName == “Foo” || Customer.Columns.LastName == “Bar”);

以下是相关的运算符重载。
public static bool operator true(Criteria<T> criteria) {
   return false;
}

public static bool operator false(Criteria<T> criteria) {
   return false;
}

public static Criteria<T> operator &(Criteria<T> lhs, Criteria<T> rhs) {
   return new AndCriteria<T>(lhs, rhs);
}

public static Criteria<T> operator |(Criteria<T> lhs, Criteria<T> rhs) {
   return new OrCriteria<T>(lhs, rhs);
}

感谢提供有效链接。事实证明,这篇文章并没有真正回答我的问题。或者说,如果它确实回答了,我还没有理解到。 - comecme

4

编辑-

阅读链接文章后,我能够得到以下输出,其中使用了true和false运算符:

op false on 1
op & on 1 -1
op true on 1
op true on -1
FALSE
op false on -1
op true on -1
FALSE
op true on 1
op true on 1
TRUE
op true on -1
op & on -1 1
op true on -1
op true on 1
TRUE

使用以下代码:
class Program
{
    static void Main(string[] args)
    {
        MyStruct b1 = new MyStruct(1); // to be considered true
        MyStruct b2 = new MyStruct(-1); // to be considered false

        Console.WriteLine((b1 && b2) ? "TRUE" : "FALSE");
        Console.WriteLine((b2 && b1) ? "TRUE" : "FALSE");

        Console.WriteLine((b1 || b2) ? "TRUE" : "FALSE");
        Console.WriteLine((b2 || b1) ? "TRUE" : "FALSE");

        Console.ReadLine();
    }
}

public struct MyStruct
{
    private int _i;

    public MyStruct(int i)
    {
        _i = i;
    }

    public static bool operator true(MyStruct ms)
    {
        Console.WriteLine("op true on {0}", ms);
        return ms._i > 0;
    }

    public static bool operator false(MyStruct ms)
    {
        Console.WriteLine("op false on {0}", ms);
        return ms._i <= 0;
    }

    public static MyStruct operator &(MyStruct lhs, MyStruct rhs)
    {
        Console.WriteLine("op & on {0} {1}", lhs, rhs);

        if (lhs)
        {
            return rhs;
        }
        else
        {
            return new MyStruct(-1); //-1 is false
        }
    }

    public static MyStruct operator |(MyStruct lhs, MyStruct rhs)
    {
        Console.WriteLine("op & on {0} {1}", lhs, rhs);

        if (lhs)
        {
            return lhs;
        }
        else
        {
            return rhs;
        }
    }

    public override string ToString()
    {
        return this._i.ToString();
    }
}

当你说第一段代码不能编译时,我不确定你的意思,尽管它没有使用true/false运算符,但我在2010 express中运行了以下代码并得到了输出:

op bool on 1
op bool on -1
False
op bool on -1
False
op bool on -1
op bool on 1
True
op bool on 1
True

代码:

class Program
{
    static void Main(string[] args)
    {
        MyStruct b1 = new MyStruct(1); // to be considered true
        MyStruct b2 = new MyStruct(-1); // to be considered false

        Console.WriteLine(b1 && b2);
        Console.WriteLine(b2 && b1);

        Console.WriteLine(b2 || b1);
        Console.WriteLine(b1 || b2);

        Console.ReadLine();
    }
}

public struct MyStruct
{
    private int _i;

    public MyStruct(int i)
    {
        _i = i;
    }

    public static bool operator true(MyStruct ms)
    {
        Console.WriteLine("op true on {0}", ms);
        return ms._i > 0;
    }

    public static bool operator false(MyStruct ms)
    {
        Console.WriteLine("op false on {0}", ms);
        return ms._i <= 0;
    }

    public static implicit operator bool(MyStruct ms)
    {
        Console.WriteLine("op bool on {0}", ms);
        return ms._i > 0;
    }

    public override string ToString()
    {
        return this._i.ToString();
    }
}

很抱歉,我后来才添加了bool运算符。加上bool后,代码确实可以编译。我会修改我的代码。 - comecme
在没有运算符&的情况下,使用运算符booltruefalse运算符永远不会被调用,只有bool会被调用。因此,我想我可以通过仅使用运算符bool来实现我想要的&&操作。但问题在于,重载truefalse运算符何时实际上会有用。 - comecme
我编辑了答案,使用了真和假运算符来展示它,但仍不确定为什么你想要使用这种方法而不是重载bool,后者会更易读。 - BrandonAGr
我不想实现特定的方法,我想知道你何时会实现“true”和“false”,以便“false”实际上会被执行。 - comecme

1
回答你的最后一个问题:“如何使我的类型能够检查两个变量是否都为true?”-只需使用&运算符。 &&的整个重点是短路,因此在不必要时不会检查第二个参数。
看看这个:
Console.WriteLine(b1 & b2); // outputs 1
Console.WriteLine(b2 & b1); // outputs 1

你实际上缺少了一个重要的部分,它将允许你使用MyStruct(带有&|)作为布尔值 - 隐式转换为bool

public static implicit operator bool(MyStruct ms) {
    return ms._i > 0;
}

这将允许您将MyStruct(以及运算符的结果)用作以下方式:
if (b1 & b2)
    Console.WriteLine("foo");

作为最后一点,也许是最重要的一点:你示例中的问题源于你想进行逻辑操作(检查两个MyStruct实例是否为true),但是你的&运算符对于这种目的实现不正确。它按照二进制算术工作,当使用参数MyStruct(1)true)和MyStruct(-1)false)调用时,会产生一个值为1MyStruct实例。因此,它基本上执行了(true & false) == true。这就是为什么在你的示例中b1 && b2b2 && b1给出不同结果的原因。任何基于此运算符的进一步逻辑都将是错误和不可预测的。.NET中实现的&&的行为,以false&为基础,证实了这一点。 编辑:您想要能够将MyStruct用作布尔值。您实现了truefalse运算符,并期望&&||按照布尔逻辑工作。但是,您使用二进制算术(在int字段上使用&)来实现&,这使得&的此实现与您期望的布尔逻辑不兼容((1 & -1) == 1,这意味着在您对MyStruct布尔值的解释中,(true & false) == false)。现在,请考虑一般情况下&&不是一个逻辑运算符(它不返回bool)-它是一个短路实现,实现为T.false(x) ? x : T.&(x, y)。请注意,在您的情况下,它返回MyStruct,您只需根据其字段的值解释为truefalse。底线:您希望&&对两个操作数进行逻辑测试,但.NET实现&&使用了您的&实现,这与您期望的布尔逻辑不兼容。

1
我不需要实现对bool的隐式转换就可以使用if(b1 & b2)。它只是在b1 & b2的结果上执行true运算符。当实现重载时唯一的区别是Console.WriteLine(b1 && b2)不会打印1-1,而是实际输出TrueFalse。如果我实现了bool转换并删除了&运算符,那么只有bool将被用到。在这种情况下,我可以删除truefalse重载,因为它们不会再被执行。 - comecme
1
你是对的,我的错,我没有测试那个。如果实现了 bool,那么 &truefalse 可以被移除(在这种特定情况下)。我猜这正是你需要的,因为 MyStruct 表现出了逻辑上正确的方式。所以你继续寻找有用的 false 实现的任务继续进行中;-) - Jakub Januszkiewicz

1

来自微软(http://msdn.microsoft.com/en-us/library/6292hy1k.aspx):

在 C# 2.0 之前,使用 true 和 false 运算符创建用户定义的可空值类型,这些类型与 SqlBool 等类型兼容。然而,现在语言提供了对可空值类型的内置支持,因此尽可能使用这些类型,而不是重载 true 和 false 运算符。

如果您只想将对象评估为布尔值,请删除运算符 true 和运算符 false 的重载,并仅使用 bool 重载。


我不明白我的true和false重载逻辑有什么问题。如果_i <= 0,false返回true,因为小于或等于零的值被认为是false值。 - comecme
你说得对,我可以删除truefalse运算符,并实现对bool的隐式转换。但这还没有回答我的另一个问题:这有什么用处呢? - comecme
我不明白逻辑上有什么问题。你是对的,我的错。这个有什么用呢?嗯,这取决于你想做什么。如果你只想将对象评估为布尔值,那么bool重载就足够了。听起来它们已经过时了,至少对于它们最初的目的(创建可空类型)而言。正如其他人所发表的,它们显然也可以用于为LINQ逻辑AND和OR制作简写符号。 - Dave Cousineau

0

真/假运算符的目的是提供布尔逻辑语义,而无需将其隐式转换为bool

如果您允许您的类型隐式转换为bool,则不再需要真/假运算符。但是,如果您只想进行显式转换或不进行转换,但仍希望将您的类型作为ifwhile表达式中的条件,则可以使用truefalse运算符。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接