Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

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

ID: 642217 • Letter: M

Question

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#?

Explanation / Answer

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)

Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Chat Now And Get Quote