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)