2011-04-14 12 views
7
public class Racional<T> 
{ 
    private T nominator; 
    private T denominator; 
    public T Nominator 
    { 
     get { return nominator; } 
     set { nominator = value; } 
    } 
    public T Denominator 
    { 
     get { return denominator; } 
     set { denominator = value; } 
    } 
    public Racional(T nominator, T denominator) 
    { 
     this.nominator = nominator; 
     this.denominator = denominator; 
    } 
    public static Racional<int> operator *(Racional<int> a, Racional<int> b) 
    { 
     return ((int)(a.nominator + b.nominator, a.denominator + b.denominator)); 
    } 
    public override string ToString() 
    { 
     return "(" + this.nominator + " " + this.denominator + ")"; 
    } 
} 

Estoy interesado en esta parte:Ayuda con operandos matemáticos en clase (C#)

public static Racional<int> operator *(Racional<int> a, Racional<int> b) 
{ 
    return ((int)(a.nominator + b.nominator, a.denominator + b.denominator)); 
} 

¿Qué pasa:

Uno de los parámetros de un operador binario debe ser el que contiene el tipo

¿Cómo puedo programar normalmente esta pieza para las operaciones matemáticas?

+0

cierto es ésta una tarea o que se va a utilizar en el código de producción? Consulte http://msdn.microsoft.com/en-us/library/microsoft.solverfoundation.common.rational%28v=vs.93%29.aspx –

+0

Es posible que desee cambiar el nombre de la clase a Rational ... –

+0

¿Por qué estás creando una clase que maneja el framework de forma inmediata? Si esto es tarea, esta es una manera horrible de hacer esto. –

Respuesta

3

La razón por la que su código no compila se explica por el error del compilador. El tipo contenedor es una definición de tipo genérico, y un tipo genérico construido de dicho tipo no se considera del mismo tipo.

Tengo algunas preguntas:

  1. Por qué debe ser del tipo Rational genérico? Un número racional es definido como un número que se puede expresar como el cociente/fracción de dos enteros (donde el denominador no es 0). ¿Por qué no hacer que el tipo no sea genérico y simplemente usar int en todo momento? ¿O piensa que el tipo se use para otros tipos integrales como long y BigInteger? En ese caso, considere usar algo como la sugerencia de Aliostad si quiere algún mecanismo de código compartido.
  2. ¿Por qué quiere que el producto de dos números racionales sea igual a la suma de sus numeradores sobre la suma de sus denominadores? Eso no tiene sentido para mí.

En cualquier caso, parece que quieren ser capaces de 'genéricamente' añadir dos instancias de un tipo 'que se puede añadir'. Desafortunadamente, actualmente no hay forma de expresar una restricción 'tiene un operador de adición adecuado' en C#.

Método n. ° 1: Una solución para esto en C# 4 es utilizar el tipo dynamic para proporcionarle la semántica deseada de "operador virtual".

public static Racional<T> operator *(Racional<T> a, Racional<T> b) 
{ 
    var nominatorSum = (dynamic)a.Nominator + b.Nominator; 
    var denominatorSum = (dynamic)a.Denominator + b.Denominator; 

    return new Racional<T>(nominatorSum, denominatorSum); 
} 

El operador lanzará si el tipo no tiene un operador de suma adecuado.


Método # 2: Otra forma (más eficiente) es el uso de la expresión de los árboles.

En primer lugar, crear y almacenar en caché un delegado que puede realizar la adición mediante la compilación de la expresión apropiada: (. El constructor estático arrojará si el tipo no tiene un operador de adición adecuada)

private readonly static Func<T, T, T> Adder; 

static Racional() 
{ 
    var firstOperand = Expression.Parameter(typeof(T), "x"); 
    var secondOperand = Expression.Parameter(typeof(T), "y"); 
    var body = Expression.Add(firstOperand, secondOperand); 
    Adder = Expression.Lambda<Func<T, T, T>> 
       (body, firstOperand, secondOperand).Compile();  
} 

Entonces emplearla en el operador:

public static Racional<T> operator *(Racional<T> a, Racional<T> b) 
{ 
    var nominatorSum = Adder(a.Nominator, b.Nominator); 
    var denominatorSum = Adder(a.Denominator, b.Denominator); 
    return new Racional<T>(nominatorSum, denominatorSum); 
} 
+0

+1, es muy interesante que esto sea posible de forma genérica. La primera vez tuve una idea de para qué expresión son buenos los árboles. –

+0

Bien, ¿por qué no int en todo el código: porque tengo ese laboratorio para mi lección de programación – user707895

1

Para resolver su problema, debe proporcionar funciones de conversión de T a algún tipo donde se define operator+ y viceversa. Suponiendo Int64 es lo suficientemente grande como en la mayoría de los casos, esto se puede hacer de esta manera:

public class Racional<T> 
{ 
    private T nominator; 
    private T denominator; 
    static Converter<T,Int64> T_to_Int64; 
    static Converter<Int64,T> Int64_to_T; 

    public static void InitConverters(Converter<T,Int64> t2int, Converter<Int64,T> int2t) 
    { 
     T_to_Int64 = t2int; 
     Int64_to_T = int2t; 
    } 

    public T Nominator 
    { 
     get { return nominator; } 
     set { nominator = value; } 
    } 
    public T Denominator 
    { 
     get { return denominator; } 
     set { denominator = value; } 
    } 
    public Racional(T nominator, T denominator) 
    { 
     this.nominator = nominator; 
     this.denominator = denominator; 
    } 
    public static Racional<T> operator *(Racional<T> a, Racional<T> b) 
    { 
     return new Racional<T>(
      Int64_to_T(T_to_Int64(a.nominator) + T_to_Int64(b.nominator)), 
      Int64_to_T(T_to_Int64(a.denominator) + T_to_Int64(b.denominator))); 
    } 

    // By the way, should this not be * instead of + ??? 
    // 
    // public static Racional<T> operator *(Racional<T> a, Racional<T> b) 
    // { 
    // return new Racional<T>(
    //  Int64_to_T(T_to_Int64(a.nominator) * T_to_Int64(b.nominator)), 
    //  Int64_to_T(T_to_Int64(a.denominator) * T_to_Int64(b.denominator))); 
    // } 



    public override string ToString() 
    { 
     return "(" + this.nominator + " " + this.denominator + ")"; 
    } 
} 

Por supuesto, esto tiene el inconveniente de que debe proporcionar la inicialización de los convertidores en algún lugar al inicio del programa, debería tener este aspecto:

Racional<int>.InitConverters(x => (Int64)x, y => (int)y); 

En un verdadero programa, es posible saber qué reemplazos posible que T que se va a utilizar. Así que uno puede proporcionar esos 3 o 4 llamadas en un constructor estático como sigue:

public static Racional() 
    { 
     Racional<int>.InitConverters(x => (Int64)x, y => (int)y); 
     Racional<short>.InitConverters(x => (Int64)x, y => (short)y); 
     Racional<Int64>.InitConverters(x => (Int64)x, y => (Int64)y); 
    } 

debería ser suficiente en la mayoría de los casos. Tenga en cuenta que esta inicialización del convertidor se repite para los 3 tipos 3 veces nuevamente, reinicializando las funciones de conversión varias veces nuevamente. En la práctica esto no debería causar ningún problema.

+0

virtual -1. ¡Una mala suposición!No compila con el error 'Uno de los parámetros de un operador binario debe ser el tipo que lo contiene' – Aliostad

+0

@Aliostad: cambió mi respuesta por completo. –

2

El problema aquí es que está definiendo un operador para Racional<int> en la clase Racional<T>. Esto no es posible. Los tipos no son los mismos, solo se puede definir el operador para Racional<T>.

Los genéricos no pueden expresar la generalización de los operadores, ya que están definidos solo para ciertos tipos. solución es crear una clase y heredar de Racional<int>:

public class IntRacional : Racional<int> 
{ 
    public static Racional<int> operator +(IntRacional a, IntRacional b) 
    { 
     return new Racional<int>() 
     { 
      Nominator = a.Nominator + b.Nominator, 
      Denominator = a.Denominator + b.Denominator 
     }; 
    } 
} 
+0

Eso no tiene sentido. ¿Puedes elaborar? –

+0

Racional y Racional son dos clases diferentes a los ojos del compilador. Y no puede redefinir el comportamiento de una clase en otra (excluyendo métodos de extensión). – SWeko

Cuestiones relacionadas