2011-11-13 17 views
9

Mi cerebro se ha convertido en gelatina, o estoy teniendo una experiencia mental, o algo así. Estoy jugando con una jerarquía de clases que se ve un poco como esto:
enter image description hereOperadores y herencia

Mi clase Money se ve así:

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

Mi Dólar operator * se ve así:

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

Ahora, si ejecuto este código de prueba, obtengo un desbordamiento de pila (wahoo!)

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

Esperaba que esto llamara recursivamente a la subclase (Dollar) operator *, lo que arrojaría un resultado definitivo ya que (Dollar * int) se define de forma no recursiva. Como esto no funciona, la alternativa es que he hecho algo tonto. ¿Por qué esto no funciona? ¿Cuál sería la forma correcta de obtener este comportamiento?

+1

Cuando obtiene un desbordamiento de pila, debe examinar la pila. Verás las mismas funciones llamándose una y otra vez. Solo eso te dirá mucho sobre lo que está pasando y por qué. – abelenky

+2

Tenga en cuenta que la recursión se produce porque en realidad está invocando 'Money.operator *', no 'Dollar.operator *'. Los operadores están * sobrecargados *, no * reemplazados *, por lo que la función que se invoca está determinada por los tipos * de tiempo de compilación * de los operandos, no por los tipos * de tiempo de ejecución *. Como 'fiveDollars' es una variable de tipo' Money', 'fiveDollars * 2' invoca la versión' Money' de 'operator *' (aunque el tipo * run-time * de 'fiveDollars' es' Dollar'.) – dlev

Respuesta

11

Usted parece haber dejado de lado .Amount

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

+1 gracias, buena captura –

4

El problema es que se espera que pueda anulación operadores en las clases derivadas y esperar dynamic binding. Esta no es la forma en que funciona en C#. Los operadores están sobrecargados y se elige la sobrecarga real en tiempo de compilación. Esto significa que el código siguiente es recursiva y llama a sí misma:

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

Otro ejemplo donde se puede ver la diferencia entre la sobrecarga de operadores y primordial método es la siguiente:

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 

En el tercer caso, C# trata a y b como objetos en lugar de enteros, por lo que utiliza el operador predeterminado == que se utiliza para objetos: comparando referencias (en este caso, las referencias de enteros encuadrados).

Esto puede hacer que resulte incómodo definir operadores en una jerarquía de clases en la que desee redefinir los operadores en clases derivadas. Es especialmente incómodo cuando el comportamiento depende de la combinación de ambos operandos, ya que C# (y la mayoría de los otros lenguajes OOP) carece de soporte para multiple dispatch. Puede resolver esto usando el patrón de visitante, pero creo que en este caso debería reconsiderar si el uso de subclases para cada moneda es la mejor solución.

+0

+1 Sí, ese parece ser el problema. Yo creo que los operadores fueron anulados, he aprendido algo aquí :) –