运算符重载和不同类型

15

我有一个名为Score的类,将会在与整数进行比较时频繁使用。根据下面的代码,我计划重载==运算符以实现这些比较。

public class Score
{
    public Score(int score) {
        Value = score;
    }

    public static bool operator ==(Score x, int y) {
        return x != null && x.Value == y;
    }

    public static bool operator ==(int y, Score x)
    {
        return x != null && x.Value == y;
    }
}

这是合理使用运算符重载的吗?

我是否应该提供运算符左右两侧的重载,以实现使用的对称性?

5个回答

11

我可能会定义一个从intScore的隐式转换,这样当你处理相等性时,你只需要处理一个类型。

public static implicit operator Score(int value)
{
    return new Score { Value = value }; // or new Score(value);
}
// define bool operator ==(Score score1, Score score2)

// elsewhere 
Score score = new Score { Value = 1 };
bool isScoreOne = (score == 1);

当你定义自己的==运算符时,一定要记得同时定义!=,并重写EqualsGetHashCode方法。


我刚刚尝试了这个,但它无法编译--无法将'=='运算符应用于类型为'int'和'Score'的操作数。 - Adrian Russell
没关系,当我清除其他重载时,我注释掉了我的类==运算符。 - Adrian Russell
在查看了我的情况后,这实际上就是我想要的。该对象可能相对简单,但它将被广泛使用。 - Adrian Russell

6
我认为在这种情况下使用运算符重载是一种奇怪的情况,但这取决于您自己。
然而,我的主要观点是,如果您重载了 == 操作符,还必须重载 != 操作符。
如果您重载了 != 操作符,则使用 x != null 将会导致 == 操作符调用 != 操作符。这本身不是问题,只要它不再使用 == 比较,否则将导致递归调用,从而导致堆栈溢出。
然而,由于很多人在重载 != 时将其实现为“not ==”,因此在您的情况下将引发堆栈溢出。
解决方案:特别是在重载 ==、!= 和 Equals() 时,最好使用 Object.ReferenceEquals(x, null); 来与 null 进行比较。

3
通常当我看到一个重载运算符时,它是将该类与自身进行比较。因此,如果您有多个实例变量,您需要找出如何将两者进行比较以确定是否相等、更大、更小等。
我不明白为什么你不直接这样做:
if(myScore.value == 5)
{
 //do stuff
}

+1,同意。在这种情况下,重载似乎没有“增加价值”。 - user166390
2
我认为暴露.value的实际实现是一个坏主意。如果代码更改以将分数测量为double,但比较应该基于舍入值,那怎么办?如果一开始使用运算符重载,您可以进行实现更改而无需更改用法。 - thelsdj
隐藏Value的实现是一个目标。@pst在某种程度上,我希望减少代码混乱。if(myScore == 5)比if(myScore.Value == 5)更易读...虽然这只是一个小问题,但也需要考虑。 - Adrian Russell
如果 Value 合同的一部分,那么就不会有任何更改。有人可能会争论说“如果从 int 更改为 float 会怎样”-- 我看不出有什么区别。 - user166390

2

这是否是运算符重载的明智使用?

如果这能让你的代码更易于理解并且不会引起问题,我想说,绝对是可以的!当然还有其他方式,但如果这样适用于你的情况,我认为没有问题。

我认为,除非你有特殊原因需要这样做(也就是说你正在使用或需要它们),否则应该不必为操作符的左右两侧提供重载。唯一真正的例外是,如果你正在编写一个框架,在那种情况下你可能会比较确定有人会需要它。


-4

简而言之 从大局来看,这不是一个明智的想法。

  1. 事实上,一个人感到需要询问或搜索这个问题已经说明了一些问题。
  2. 很少有人关心这个问题,这表明可能有一种方法可以避免你需要开始询问这个问题的情况。
  3. 这最后两点至少应该促使你重新考虑一些事情,无论它们看起来多么不相关。

考虑尽早为软件组件的所有输入分配类型,并将其放在域逻辑之外。这样,我们通常称之为“领域模型”的东西就不必担心这个问题了。
在您的情况下,这意味着将所有分数都包装在Score对象中。因为它们就是分数。做出例外的需要始终至少是可疑的,并且可以作为代码异味的标志。
下一步是将您的“领域模型”视为普通模块集合。将使用有形原语的(太常见的)I/O接口视为履行不同/独立角色。这将自然而然地(几乎像魔术一样)降低所有组件(例如类)的个体复杂性。
这样做的一个副作用是,您可能会发现这个问题变得毫无意义。“如果我甚至不确定整数是否为分数,为什么要比较分数和原始整数?如果我确定了,那么为什么之前没有这样说(例如通过装箱)?”

更进一步,您可能会意识到,您的许多算法甚至不需要知道如何计分:很可能它们中的大部分只需要能够比较它们,以查看它们是否相等或哪个更高。我并不是说修改得分的方式的灵活性将来会有用,但作为一个规则,如果它不应该重要,则没有必要仅仅为了可爱而将代码绑定到实现上。也许令人惊讶的是,这也适用于原始类型,例如整数,甚至数字的概念(您不需要在所有需要比较两个分数的情况下关心它)。SOLID原则提供了一个很好的指南,直到您可以自己形成直觉。

反驳:人们总是可以引用性能的论点来反驳这种思路。根据我的经验,我发现JIT会优化和内联任何影响性能的东西(尤其是对于像相等比较之类的琐碎事情),当它不这样做时,几乎总有一种以明智的方式抽象出性能关键代码的方法。如果派遣是一个问题,不要犹豫地封装您的类型。

在测量和找到瓶颈之后,如果一个正确的设计无法通过替代但同样合理的设计来修复,那么就需要诚实地承认自己遇到了语言限制。在这种情况下,应该考虑将代码提取出来并用更适合的语言编写,或者在现有语言中进行修改,但要对该代码进行详细注释。

最后,我想说我以前确实做过你建议的事情,而且我只能感激那段代码从未进入生产环境。一次似乎不会有什么害处,但如果到处都这样做,那种不安的感觉就会落定,并且只会产生挫败感,直到你最终意识到为什么这是错误的(根据上述考虑)。如果你好奇,我是为几乎总是包装单个基元的实体键表示类型做的。再次强调,这似乎是个明智的想法,你可能仍然这么认为,但我坚信现在不是,尽管我以前没有听说过。也许这是你需要亲身经历才能真正领悟的事情。


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