Ok, I need to implement, among others, these two particular types: ComplexNumber<T>
and Matrix<T>
. T
can be one of the following: a rational number, a real number or integers.
In the System namespace I have a good representation for real numbers (decimal
), integer numbers (int
or long
). Numerics.ComplexNumber
does not cut it becuase internally RealPart
and ImaginaryPart
are double
and I can't afford, in this particular case, the representation error of this type.
Now, the probem is of course that there is no way to constraint the generic parameter T
to mathematical "valid" types. That is, I can't do the following:
public struct ComplexNumber<T>
{
readonly T realPart;
readonly T imaginaryPart;
public static ComplexNumber<T> Add(ComplexNumber<T> left, ComplexNumber<T> right)
{
return new ComplexNumber<T>(left.realPart + right.realPart, left.imaginaryPart + right.imaginaryPart); //Compile time error. No operator defined for T
}
}
So I need a workaround. Although performance is not a goal in itself, I'd like the code to work reasonably well, but above all, I'd like it to be the most elegant solution possible. Right now I've come up with two possibilities:
A Numeric
abstract base class similar to:
public abstract class Numeric
{
protected abstract Numeric _Add(Numeric right);
protected abstract Numeric _Subtract(Numeric right);
public static Numeric Add(Numeric left, Numeric right) { return _Add(this, right); }
public static Numeric Subtract(Numeric left, Numeric right) { return _Subtract(this, right);
}
Now I could do:
public sealed class RationalNumber: Numeric
{
readonly long numerator, denominator;
protected override Numeric _Add(Numeric right) { //Rational addition implementation }
}
And then Matrix<RationalNumber>
or Matrix<ComplexNumber>
would work.
The other option is to do it through an interface:
public INumeric
{
INumeric Add(INumeric right);
INumeric Subtract(INumeric right);
}
public struct RationalNumber: INumeric
{
readonly long numerator, denominator;
public static RationalNumber Add(RationalNumber left, RationalNumber right) { //Rationa add implementation }
INumeric INumeric.Add(INumeric right) //explicit to not clog up the type and to convey the idea of a framework mechanism.
{
if (!(right is RationalNumber))
throw new ArgumentException();
Add(this, (RationalNumber)right);
}
The interface option lets me implement RationalNumber
as a struct wich is, IMHO, more consistent with how numeric types are implemented in the framework. Also the types themselves are pretty lightweight and will typically have short lives. The Numeric
base class solution seems like a whole lot more heavyweight, but I'm not really an expert to exactly quantify the advantages of one over the other when it comes to:
- Code qualitiy and future extensibility and maintenance
- Performance and memory consumption (curiosity more than anything else)
- A better solution?
If anyone can shed a little bit of light, I'd appreciate it.