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

I'm interested in this part :

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

What's wrong:

One of the parameters of a binary operator must be the containing type

How I can normaly code this part for mathematic operations?

ChrisF
  • 134,786
  • 31
  • 255
  • 325
user707895
  • 351
  • 1
  • 4
  • 15
  • BTW is this a homework or you are going to use it in production code? Check out http://msdn.microsoft.com/en-us/library/microsoft.solverfoundation.common.rational%28v=vs.93%29.aspx – Lukasz Madon Apr 14 '11 at 12:04
  • You might want to change the class name to Rational... – Roy Dictus Apr 14 '11 at 12:05
  • Why are you creating a class that is handled by the framework out of the box? If this is homework this is a horrible way of doing this. – Security Hound Apr 14 '11 at 12:06
  • @Roy: I assume it's in language other than English. At least he posted his *actual* code, rather than risk irrelevant typographical errors appearing. – Cody Gray - on strike Apr 14 '11 at 12:07
  • Since it looks like what you really want is a rational of int all along, why are you even using generics? – Smur Apr 14 '11 at 13:16
  • @Felipe: To allow decimal, BigInteger and/or float rationals, maybe? – Mauricio Apr 14 '11 at 13:58
  • 3
    Hold on a moment here, you're saying that 1/2 * 2/3 = 3/5 ? This code not only doesn't compile, it doesn't make any sense. And you're saying that there are rationals that have arbitrary types as the numerator and denominator? **Why is this generic at all**? A rational is defined as the ratio of two integers. You could have integers, strings, exceptions, and so on. This code doesn't make any sense to me. Can you explain? – Eric Lippert Apr 14 '11 at 15:57
  • This is homework(lab), more I don't know English enough – user707895 Apr 14 '11 at 17:55
  • @Cody & Sergio: Nominator and Denominator are Spanish words?? :-) – Roy Dictus Apr 15 '11 at 08:06
  • @Eric: I assume he wants to be able to use Int16, Int32, Int64 as possible numerator and denominator types. – Roy Dictus Apr 15 '11 at 08:08
  • @Kyte Check again his code. I see that he's TRYING to compile something t hat would return him a Rational. It would seem that he's only interested in that, wouldn't it? – Smur Apr 15 '11 at 17:33
  • @Felipe: My guess went long the lines of: "I want to have operator* for Rational, but I don't know how. So in the meantime I'll just define it for Rational". – Mauricio Apr 15 '11 at 19:02
  • @Kyte Yeah well, he wrote it like that when he was showing us the class, so I assumed he wanted that method like that. – Smur Apr 15 '11 at 20:15

3 Answers3

3

The reason your code doesn't compile is explained by the compiler error. The containing type is a generic type definition, and a generic type constructed from such a type is not considered to be the same type.

I have a few questions:

  1. Why must the Rational type be generic? A rational number is defined as a number that can be expressed as the quotient / fraction of two integers (where the denominator is not 0). Why not make the type non-generic and simply use int throughout? Or do you intend that the type be used for other integral types such as long and BigInteger? In that case, consider using something like Aliostad's suggestion if you want some code-sharing mechanism.
  2. Why do you want the product of two rational numbers to be the equal to the sum of their numerators over the sum of their denominators? That doesn't make sense to me.

In any case, you appear to want to be able to 'generically' add two instances of an 'addable' type. Unfortunately, there currently isn't any way to express a 'has a suitable addition operator' constraint in C#.

Method #1: One workaround for this in C# 4 is to use the dynamic type to give you the desired "virtual operator" semantics.

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

The operator will throw if the type doesn't have a suitable addition operator.


Method #2: Another (more efficient) way is to use expression-trees.

First, create and cache a delegate that can perform the addition by compiling the appropriate expression:

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

(The static constructor will throw if the type doesn't have a suitable addition operator.)

Then employ it in the operator:

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);
}
Ani
  • 111,048
  • 26
  • 262
  • 307
2

The issue here is you are defining an operator for Racional<int> in the class Racional<T>. This is not possible. The types are not the same, you can only define operator for Racional<T>.

Generics cannot express generalization of operators since they are defined only for a certain types. Solution is to create a class and inherit from 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
        };
    }
}
Aliostad
  • 80,612
  • 21
  • 160
  • 208
1

To solve your issue, you need to provide conversion functions from T to some type where operator+ is defined and vice versa. Assuming Int64 is big enough in most cases, this can be done this way:

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

Of course, this has the drawback that you must provide the initialization of those converters somewhere at the program start, should look like this:

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

In a real program, you may know which possible replacements for T you are going to use. So one can provide those 3 or 4 calls in a static constructor like this:

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

should be sufficient in most cases. Note that this converter initialization is repeated for all 3 types 3 times again, re-initializing the conversion functions multiple times again. In practice this should not make any trouble.

Doc Brown
  • 19,739
  • 7
  • 52
  • 88