I've encountered a problem familiar to veteran C# developers: whether to use a class or a struct for a particular design. So I'm going to follow my own advice and develop first for elegance and second for execution speed.
The specifics: As I mentioned earlier, I'm re-writing the way the Inner Drive Extensible Architecture handles measurements. I've identified 16 scenarios in which I use measurement classes, and I want them to be as intuitive as possible. So, for example, scenario #1 is "instantiate a new measurement:"
Length meters = new Length(15, typeof(Meter));
meters.ToString() == "15 m"
Another scenario is "add two measurements:"
longLength = length1 + length2;
I also expect to use the items in lists:
SortedList<Length> lengths = new SortedList<Length>();
This last scenario requires that the Length class implement IComparable (or IComparable<T>), on the one hand, and that it be lightweight, on the other.
Here's where the class v. struct problem comes in. Structs are lightweight, because the .NET runtime places them on the stack directly instead of placing pointers to them on the stack. In other words, using a class requires indirection, and that takes enough time for you to notice if you're dealing with hundreds or thousands of them.
Therefore, my first attempt, which I am discarding, was to create the measurement classes (Length, Volume, Mass, etc.) as structs.
I soon ran into several problems that convinced me to create an abstract Measurement class that the individual types of measurement will inherit from:
- Lots of duplicate code. Adding, subtracting, equality, assignment—they're the same, with minor and specific differences, for each of the measurement types. You can't easily share code between structs. Using inheritance, about 90% of the code can go into the abstract class.
- Validation in multiple constructors. I need to validate the initial data used to create measurements, so that attempting to create a length of Fahrenheit throws an exception. Since the entire struct must be initialized before control leaves the constructor, this requires that I put validation code in several places, depending on how the constructors work.
- Finally, now that .NET allows property setters and getters to have different visibility (public get with protected set), it's much easier to make classes immutable without making them structs.
If there's a tremendous performance hit, I'll worry about that when the design is stable.