Операторы и наследование - PullRequest
9 голосов
/ 13 ноября 2011

Мой мозг превратился в желе, или у меня опыт с ума , или что-то в этом роде.Я работаю с иерархией классов, которая выглядит примерно так:
enter image description here

Мой 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;
}

Я ожидал, что это будет рекурсивно вызывать подкласс(Доллар) operator *, который вернул бы определенный результат, поскольку (Доллар * int) определяется не рекурсивно.Поскольку это не работает, альтернатива в том, что я сделал что-то глупое.Почему это не работает?Каков был бы правильный способ получить такое поведение?

Ответы [ 2 ]

11 голосов
/ 13 ноября 2011

Вы, кажется, пропустили .Amount

public static Money operator *(Money multiplicand, int multiplier)
{
    var result = multiplicand.Amount * multiplier;
    return result;
}
4 голосов
/ 13 ноября 2011

Проблема в том, что вы ожидаете, что вы можете переопределить операторы в производных классах и ожидать динамическое связывание .Это не так, как это работает в 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 # рассматривает a и b как объекты вместо целых чисел, поэтому он использует оператор == по умолчанию, который используется для объектов: сравнение ссылок (в данном случае ссылки целых чисел в штучной упаковке).

Это может затруднить определение операторов в иерархии классов, где вы хотите переопределить операторы в производных классах.Это особенно неудобно, когда поведение зависит от комбинации обоих операндов, поскольку в C # (и большинстве других языков ООП) отсутствует поддержка многократная диспетчеризация .Вы можете решить эту проблему с помощью шаблона посетителя, но я думаю, что в этом случае вам следует пересмотреть вопрос, если использование подклассов для каждой валюты является наилучшим решением.

...