Did it ever happen to you that you hit a problem that you couldn’t solve, and even you’re convinced that it’s impossible, but then just by telling about it to someone you find the solution? I’m sure you did, it happens a lot. A boss I once had used to call this phenomenon “talking to a glass of water”: it would as well be a glass of water that you’re talking to for finding the answerJ
Anyway, the reason I’m talking about this phenomenon is that I started to write this post with the following title:
Frustrating limitations of C# with regard to Value types, operator overloading, interfaces and generics
But while writing it, I found how I can go around these limitations and solve the problems I had.
So here it is:
The project I’m working on these days is a business application that makes a heavy use of data that represent things like money, quantity, percents, pieces (like quantity but integer), and also customer club membership points, which is pretty similar to money in many aspects, but also have some different characteristics.
Because of each these concepts are represented as a single value, there are no classes or structs defined for them and usually they’re represented directly using decimal, int or long. Even though this is pretty straight-forward and in most projects I’ve seen it was used this way, there are some disadvantages of using the basic data types for these concepts:
1. Maintainability: using the basic types directly makes the code tightly bound to that specific data type. For example, if for some reason in the future you’ll come to a conclusion that you prefer to use double instead of decimal (maybe because of performance), it would be very difficult to track all the places that you need to change.
2. Error prone: the base numeric data types support all the arithmetic operators, even though in some cases it doesn’t make sense for specific concepts. For example: usually it doesn’t make much sense to add two percentage values (e.g. 20% + 30%), or do something like this:
decimal price = 200m;
decimal percentsDiscount = 10m;
decimal total = price – percentsDiscount;The ‘-’ operator between money and percents either should be prohibited or be defined as: money *(100-percent)/100
3. Code clarity and readability: if you use different types for Money, Quantity and Percents then it makes the code and especially its intension clearer.
So it was clear that we should change all (or most) of the basic types to user-defined types, and define operator overloading on them. It would make sense in this situation to use value-types (structs) that merely wrap the underlying type and not reference-types (class). The reasons are both performance (they’re used a lot in very complicated calculations in the critical chain), and to avoid possible nulls.
Up to here everything looks great. But now the problems began to arise: there were at least two areas in the code that were using decimals that could represent different concepts at different occasions. One of these areas was to find whether a certain threshold has been reached: the threshold could be either on money, quantity or pieces. The other one was using either money or points.
The obvious solution I tried was to define an interface for these purposes (e.g. IThreasholdable and IAmount), and makes the concrete structs implement them as necessary. And also make the relevant classes and methods use type arguments (generics) with “where T: IThresholdable” or “where T: IAmount” constraints.
Now here comes the first problem: you can’t define operators on interfaces! Only on concrete class or struct. Note that this includes casting operators.
I don’t remember exactly all the combinations I tried and what caused me to hit what problem, but in general I tried to use class instead of struct, I tried to make the interfaces themselves generics, and probably few other variations, I tried to avoid operator overloading and only use methods, but eventually I stumbled into one of the following problems, or some other problems that I can’t recall right now:
1. The code wasn’t truly type-safe (I had to check that a correct type is given at run-time instead of preventing it at compile-time)
2. I couldn’t initialize an instance of a generic type, because C# only supports constraints like “where T : new()”, but not like “where T: new(T)” or “where T: new(IThresholdable)”
3. I couldn’t use operator overloading because they’re static and can’t be used on interfaces
4. Operator overloading can’t use generics
5. structs can’t inherit from other structs
So now, after talking to you, my dear glass of water, I realize that what I should do is as follows:
Define the generic concepts of Thresholdable and Amount using their own structs; define the interfaces IThresholdable and IAmount with a single property get that returns the corresponding concrete struct. In addition, define implicit cast operators between these types and the more specific types (Money, Points, Quantity, etc).
Now, Thresholdable and Amount will define their relevant operators, so the operators shouldn’t be defined on the interface, and we’ll only work with concrete types. Eventually these methods won’t be able to return back the more specific types, but the caller would cast it back implicitly using the implicit cast operators.
As my glass of water helped me find the solution for my problem, it still needs to be proven in real life. I already had cases that I thought I found a solution to this problem but when I came to implement it I stumbled into one of the other problems. I hope this time it wouldn’t be the case. I’ll let you know once I’ll do it!