Creating a custom type is fairly simple, but then you need to consider the following:
- Whether you want to make it COM visible
- How the type is going to perform serialization
- Whether its string representation should be customizable
- Providing all the operator overloads so it behaves like a built in type.
- Implicit and explicit conversions
- Comparison to other types
- Equality
- Persistence to various databases
- WCF/Remoting behaviour
Soon, something which you thought should take no more than 20 lines of code, could well exceed several thousands lines of code!
I once had to implement a Fraction type. I did so by having two fields of type long to accommodate the necessary precision: numerator and denominator (since most of the CPU nowadays are 64 bit using an int32 would unlikely bring any performance benefits).
Be prepared for a lot of “boring” code. What really helped me to organise the code was the use of partial classes. I would have a partial class for each interface or specific functionality I was trying to implement. For example all the operator methods would go into a separate file Function.Operators.cs.
One thing to consider when writing operations methods is that each operations method should also be exposed as a simple static method to ensure maximum compatibility with languages other than C#, that essentially doubles your source code for operations.
Then I would do the same for each of the interfaces it had to implement. And there are a lot of them if you want your type to be at home in .NET:
[StructLayout(LayoutKind.Sequential)]
[Serializable]
public readonly partial struct Fraction :
IEquatable<Fraction>,
IEquatable<long>,
IEquatable<float>,
IEquatable<double>,
IEquatable<decimal>,
IComparable<Fraction>,
IComparable<long>,
IComparable<float>,
IComparable<double>,
IComparable<decimal>,
IConvertible
Bear in mind that I didn’t have to care about it being used from COM, otherwise I would have had to add another attribute [ComVisible(true)] and then deal with consequences of having to make it usable from COM.
I also had to provide some default values for the type, similar to what you would find in double, float, int etc:
public static readonly Fraction Zero = new Fraction(0, 1);
public static readonly Fraction One = new Fraction(1, 1);
public static readonly Fraction MinusOne = new Fraction(-1, 1);
public static readonly Fraction SmallestFraction = new Fraction(1, long.MaxValue);
public static readonly Fraction Pi = new Fraction(3126535, 995207);
public const long LargestDenominatorThanCanBeReduced = 3037000499;
Obviously the code was fully covered in unit and performance tests to ensure you don’t break anything along the way and don’t introduce a change that negatively impacts the performance.
Because it was for one of the clients I simply cannot take the code I have written. I have to rewrite the code from memory all over again. I think I’m 50% there.
To give you a feeling for what it takes have a look at the repository on github: https://github.com/ebalynn/Balynn.Maths.Fraction and bear in mind it’s only 50% done
Leave a Reply