【问题标题】:Operators and inheritance运算符和继承
【发布时间】:2023-04-02 08:11:01
【问题描述】:

我的大脑变成了果冻,或者我有一种精神错乱的经历,或者其他什么。我正在修改一个看起来有点像这样的类层次结构:

我的Money 类如下所示:

public abstract class Money
{
    public int Amount { get; set; }

    public static bool operator ==(Money leftSide, Money rightSide)
    {
        // Money can only be equal if it is in the same currency.
        if (leftSide.GetType() != rightSide.GetType()) return false;
        return leftSide.Amount == rightSide.Amount;
    }

    public static bool operator !=(Money leftSide, Money rightSide)
    {
        // If the currencies are different, the amounts are always considered unequal.
        if (leftSide.GetType() != rightSide.GetType()) return true;
        return leftSide.Amount != rightSide.Amount;
    }

    public static Money operator *(Money multiplicand, int multiplier)
    {
        var result = multiplicand * multiplier;
        return result;
    }

    public static Dollar Dollar(int amount)
    {
        return new Dollar(amount);
    }

    public static Franc Franc(int amount)
    {
        return new Franc(amount);
    }
}

我的美元operator * 看起来像这样:

public static Dollar operator *(Dollar multiplicand, int multiplier)
{
    var result = multiplicand.Amount * multiplier;
    return new Dollar(result);
}

现在,如果我运行这个测试代码,我会得到一个堆栈溢出(哇哦!)

{
    Money fiveDollars = Money.Dollar(5);
    Money timesTwo = fiveDollars*2;
}

我原以为这会递归调用子类 (Dollar) operator *,这将返回一个确定的结果,因为 (Dollar * int) 是非递归定义的。由于这不起作用,另一种选择是我做了一些愚蠢的事情。为什么这不起作用?获得这种行为的正确方法是什么?

【问题讨论】:

  • 当您遇到堆栈溢出时,您应该检查堆栈。您会看到相同的函数一遍又一遍地相互调用。仅此一项就可以告诉您很多有关发生的事情和原因的信息。
  • 请注意,发生递归是因为您实际上是在调用Money.operator*,而不是Dollar.operator*。运算符是重载,而不是覆盖,因此调用的函数由操作数的编译时类型决定,而不是运行时类型。由于fiveDollarsMoney 类型的变量,所以fiveDollars * 2 调用operator*Money 版本(即使fiveDollarsrun-time 类型是Dollar。 )

标签: c# oop architecture operator-overloading


【解决方案1】:

你好像漏掉了.Amount

public static Money operator *(Money multiplicand, int multiplier)
{
    var result = multiplicand.Amount * multiplier;
    return result;
}

【讨论】:

    【解决方案2】:

    问题是您希望可以覆盖派生类中的运算符并期望dynamic binding。这不是它在 C# 中的工作方式。运算符是重载,实际重载是在编译时选择的。这意味着以下代码递归并调用自身:

    public static Money operator *(Money multiplicand, int multiplier)
    {
        var result = multiplicand * multiplier;
        return result;
    }
    

    另一个可以看出运算符重载和方法覆盖之间区别的例子是:

    int a = 5;
    int b = 5;
    
    Console.WriteLine(a == b); // true
    Console.WriteLine(a.Equals(b)); // true
    Console.WriteLine((object)a == (object)b); // false
    Console.WriteLine(((object)a).Equals((object)b)); // true
    

    在第三种情况下,C# 将ab 视为对象而不是整数,因此它使用用于对象的默认== 运算符:比较引用(在这种情况下是装箱整数的引用) .

    这会使在您想要重新定义派生类中的运算符的类层次结构上定义运算符变得很尴尬。当行为取决于两个操作数的组合时尤其尴尬,因为 C#(和大多数其他 OOP 语言)缺乏对 multiple dispatch 的支持。您可以使用访问者模式来解决这个问题,但我认为在这种情况下,您应该重新考虑是否为每种货币使用子类是最佳解决方案。

    【讨论】:

    • +1 是的,这似乎确实是问题所在。我确实认为操作符被覆盖了,我在这里学到了一些东西:)
    猜你喜欢
    • 2011-07-08
    • 1970-01-01
    • 2011-12-05
    • 2015-06-27
    • 2010-10-14
    • 2021-06-07
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多