Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

My question is in a sense a follow up of this post on Time Series in .Net.

Ideally, you would like to expand the basic TimeSeries<T> class by something like NumericTimeSeries<T>, where you would want to make sure that the type T implements the common numerical operators +, -, *, `/', ...

I am not sure what would be the most intelligent way to do this.

My first idea is to create an abstract class Number, with abstract methods Add(Number n1,Number n2), Multiply(Number n1,Number n2), Subtract(Number n1,Number n2), Divide(Number n1,Number n2) (and so on). I would then call these methods in the operator overloads.

I can then create concrete classes such as IntNumber, DoubleNumber, MatrixNumber, ComplexNumber (and so on).

The problem is that in the methods implementation, I would have to make an awful switch to determine what the class of each parameter is, and it's not very elegant.

Would you have done it the same way?

Has something already be done?

In F# I would use and inline functions, or pattern matching using discriminated unions. But what should I do in C#?

EDIT:

There was an awesome answer by Olivier Jacot-Descombes. However, I would like to specify a feature.

The numbers can apply the operator between them (you can multiply a matrix with a scalar for example). The problem with this is that you cannot know in advance the type of the result; multiplying a vector (transposed) and a vector returns a double...

share|improve this question
    
    
This kind of logic is (relatively) easy in C++. –  DeadMG Jan 1 '12 at 0:15
    
And a little holding of the compiler's hand can make it work in C# too. –  DeadMG Jan 1 '12 at 0:38
    
Multiplying a matrix with a vector returns another vector, not a scalar. –  Ben Voigt Jan 1 '12 at 0:47
    
@BenVoigt: oups, yeah I meant two vecotrs.... just corrected that. –  SRKX Jan 1 '12 at 7:18
add comment

3 Answers

up vote 4 down vote accepted

It is possible to do it with some tricks. First, we define an abstract Number class with a generic parameter T and the funny generic parameter constraint where T : Number<T>. This might look like an endless recursion, but does in fact just mean that T must derive from Number<T>. We need the generic parameter for the implicit conversion trick:

public abstract class Number<T>
    where T : Number<T>
{
    public static Number<T> operator +(Number<T> x, Number<T> y)
    {
        return x.Sum(y);
    }

    // Automatic conversion from Number<T> to T
    public static implicit operator T(Number<T> x)
    {
        return (T)x;
    }

    // Each derived type must supply its own implementation
    internal abstract Number<T> Sum(T x);
}

Number also defines an overload for the "+" operator. Since this operator is static, we cannot override it in deriving classes. Therefore we declare an abstract Sum method, that the "+" operator uses to calculate its sum. We also implement an implicit conversion from Number<T> to T.

Now let us implement a class for integer numbers that derives from Number<T>:

public class IntNumber : Number<IntNumber>
{
    private int _n;

    public IntNumber(int n)
    {
        _n = n;
    }

    public int Value { get { return _n; } }

    // Automatic conversion from int to IntNumber
    public static implicit operator IntNumber(int x)
    {
        return new IntNumber(x);
    }

    internal override Number<IntNumber> Sum(IntNumber x)
    {
        return new IntNumber(_n + x._n);
    }

    public override string ToString()
    {
        return _n.ToString();
    }
}

The important part is the implementation of the Sum method. The implicit conversion from int to IntNumber is optional. Overriding the ToString method makes it easier to display IntNumber. We also declare a constructor and a Value property that returns the integer number.

We can do the same with complex numbers:

public class ComplexNumber : Number<ComplexNumber>
{
    private double _re, _im;

    public ComplexNumber(double re, double im)
    {
        _re = re;
        _im = im;
    }

    public double Re { get { return _re; } }
    public double Im { get { return _im; } }

    public static ComplexNumber operator +(ComplexNumber x, ComplexNumber y)
    {
        return x.Sum(y);
    }

    // Automatic conversion from double to ComplexNumber
    public static implicit operator ComplexNumber(double x)
    {
        return new ComplexNumber(x, 0);
    }

    // Automatic conversion from IntNumber to ComplexNumber
    public static implicit operator ComplexNumber(IntNumber x)
    {
        return new ComplexNumber(x.Value, 0);
    }

    internal override Number<ComplexNumber> Sum(ComplexNumber x)
    {
        return new ComplexNumber(_re + x._re, _im + x._im);
    }

    public override string ToString()
    {
        return "(" + _re + "+" + _im + "i)";
    }
}

The operator overload in the base class is required in order to handle generic typed numbers (see Calculator), the one in the derived class is required, because the automatic conversion from IntNumber to ComplexNumber only works with this one.

Now let us declare a generic calculator that operates on any of our number types. (It is just a simple replacement for your NumericTimeSeries for test purposes).

public class Calculator<T>
    where T : Number<T>
{
    public T GetSum(T x, T y)
    {
        return x + y; // <== You can add any numbers with generic type T with "+"
    }
}

Note that we can just add two numbers of a generic type with the "+" operator. Because of the implicit operator declared in Number<T> we do not need any castings!

Let us test the addition of generic number types with the generic calculator and the direct addition of different number types:

IntNumber i1 = new IntNumber(2);
IntNumber i2 = new IntNumber(3);
var intCalculator = new Calculator<IntNumber>();
Console.WriteLine(intCalculator.GetSum(i1, i2)); // ==> 5
Console.WriteLine(intCalculator.GetSum(4, 6)); // ==> 10

ComplexNumber c1 = new ComplexNumber(2, 7);
ComplexNumber c2 = new ComplexNumber(3, -1);
var complexCalculator = new Calculator<ComplexNumber>();
Console.WriteLine(complexCalculator.GetSum(c1, c2)); // ==> (5+6i)

Console.WriteLine(c1 + 100.5); // ==> (102.5+7i)
Console.WriteLine(c1 + i1); // ==> (4+7i)
Console.WriteLine(i1 + c1); // ==> (4+7i)
share|improve this answer
    
That's a GREAT answer. The only weakness is that, with this model, I cannot use operators with different concrete classes like Number<T> * Number<U> where T != U. Do you have any suggestion in this case? –  SRKX Dec 30 '11 at 17:37
    
You can define additional conversion operators. For instance in ComplexNumber you can define public static ComplexNumber operator +(ComplexNumber x, IntNumber y) and public static ComplexNumber operator +(IntNumber x, ComplexNumber y). You can also add additional conversions (from IntNumber to ComplexNumber for instance), which makes conversions easier. But these will not work on the generic parameter T. –  Olivier Jacot-Descombes Dec 30 '11 at 19:08
    
I did some fine-tuning in order to allow to mix IntNumber and ComplexNumber. I had to add a "+"-operator overload as well as an implicit conversion from IntNumber to ComplexNumber to the ComplexNumber class. The operator overload in the base class is required in order to handle generic typed numbers (see Calculator), the one in the derived class is required, because the automatic conversion from IntNumber to ComplexNumber only works with this one. –  Olivier Jacot-Descombes Dec 31 '11 at 11:47
    
great! thanks a lot. –  SRKX Dec 31 '11 at 11:52
    
You can solve your matrix vector problem with this operator overload in the Matrix or the Vector class: public static double operator *(Vector x, Matrix y) { ... } –  Olivier Jacot-Descombes Dec 31 '11 at 12:07
show 3 more comments

Operator overloading in .Net is wonky, and I would say in this case is more effort than its worth, I would not bother with it. If you don't want to do a bunch of null checks (and who does) -

return Object.ReferenceEquals(null, leftIntNum) ? //because someone could have always overloaded == and done it wrong
null : 
leftIntNum.MultiplyBy(rightIntNum)`

You can use extension methods

  public static NullMultiplyBy<T1, T2>(this T1 numerator, T1 denominator)
   where T1 : Number<T2> {
    return Object.ReferenceEquals(null, numerator) ? 
    null : 
    numerator.MultiplyBy(rightIntNum)`    
  }

Other than that, expose an interface, not an abstract class. You can still use an abstract base class, but avoid referencing it in your method signatures.

share|improve this answer
add comment

My way of dealing with this is to run screaming from the room.

No, I'm not kidding, exactly. I've seen people try to use operator overloading in several different projects. In each of those projects, there came a point where the programmers lost days, as in "multiple whole days", trying to figure out a problem that turned out to be caused by the "wrong" overloaded operator getting called in some situation where it wasn't expected. This is in addition to the hours it takes to set up all the conversions and versions of the operators so that you THINK it works.

"MyNumber.Add(otherNumber)" looks a little clunky, but it's readable. You know what's happening and what code is getting called. "MyNumber + otherNumber" could be any of half a dozen different things, and you have no idea which one it is until you get down into the assembly.

Don't ever use operator overloading.

share|improve this answer
    
Then your observed programmers sucked. No, really. There's no difference between overloading Add and overloading operator+- the overload resolution algorithm is identical, and there is no semantic constraint whatsoever on Add() compared to + that makes it do something you expect. They would have run into an identical problem using Add(). –  DeadMG Jan 1 '12 at 0:17
    
Here, kid. Go read Spolsky on the subject. joelonsoftware.com/articles/Wrong.html –  mjfgates Jan 2 '12 at 10:56
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.