3

Context

Suppose I'm writing an app about cakes.
I need to store the weight of cakes in kg and their FCR (frosting/chocolate ratio. I made that up). I can store these values as float. The problem I see with that is that I can assign a weight-in-kg value to an FCR field.

C#'s type system can prevent errors like this. If I create a class WeightInKg and a class FrostingChocolateRatio, I won't be able to assign one to the other.

Issue

I will then need to implement all numerical operators (+ - * / > < == etc) again. These are already annoying to implement, because they are only mere wrappers over the functionality of float. However, as these structs are both based on float, all those wrapper methods are virtually identical. This will again be the case for every other such float-based type.

What I have tried / thought about

  • Good old OO inheritance. An abstract class FloatValue can provide all the numerical operations, but these then return the FloatValue type instead of the (type safe) subclass type. I also feel like a struct should be used for something that is inherently a bare-bones value, and structs don't support sub-classes.
  • Generics. I am currently using this; a struct Quantity<T> with all the numerical operations implemented. For T, I then use empty "marker classes", which only exist to identify a certain type of quantity (e.g. FrostingChocolateRatio). This works, but constantly using Quantity<WhatIReallyWant> is awkward and produces more visual clutter in the code.

Question

Is this as close as I can get to what I want, or are there cleaner ways to have such type-safe values in C#?

Addition

As PaulF mentioned, FrostingChocolateRatio is not an ideal example because the math works differently for ratios. However, I'm out of creativity for today; just assume it does exactly the same as WeightInKg.
The point is that there are several different types of values which behave exactly like float, but it doesn't make sense to add a centimeter to a liter.

Raphael Schmitz
  • 551
  • 5
  • 19
  • 1
    How is a `WeightInKg` different from a `FrostingChocolateRatio`? They're both just `float` values underneath. So they're not really different types, just different values. It's like if you have a product with a name and description and you worry about them both being `string`. – juharr Oct 25 '18 at 14:55
  • 2
    You're grossly overthinking the problem, it seems. There always will be a way to interchange 2 numbers: regardless of how deep you shove them down the type hierarchy they're just numbers. – icebat Oct 25 '18 at 14:59
  • 5
    Use the [right kind of language](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure) if this is important to you. – Hans Passant Oct 25 '18 at 15:17
  • 1
    Well you do actually need to implement the numerical operators differently - adding two weights is simple enough, but adding two ratios is different => 0.8 + 0.2 = 0.5 NOT 1.0 (if the ratio 0.8 is equivalent to 8 parts frosting to 10 parts chocolate & 0.2 is equivalent to 2:10 ratio, then the sum is 10 parts frosting to 20 parts chocolate) – PaulF Oct 25 '18 at 15:19
  • @juharr Yes, they are both just `float` values underneath. However, when you're asking "_How is a WeightInKg different from a FrostingChocolateRatio_", then you must not have eaten a lot of cake in your life :D – Raphael Schmitz Oct 25 '18 at 15:47
  • @icebat My goal is not to fight against programmers who do their best to misuse the code, but to help well-meaning programmers who just happen to [trip up on something as trivial as a typo](https://en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure). – Raphael Schmitz Oct 25 '18 at 16:04
  • 2
    @HansPassant Programming is my job, though. I can't just go over to my boss and tell him "_Yeah I wanna use F# now, let's put the client on hold for a couple weeks while we learn a new language and port all our code and yeah I guess let's try and find an F# 3D-Graphics engine that fulfills our needs first._" F# always seemed pretty cool to me, but it's not the answer to "how to do [x] in C#?". – Raphael Schmitz Oct 25 '18 at 16:23
  • @PaulF Good point! I did not think of that at all. I added a note about that. – Raphael Schmitz Oct 25 '18 at 16:32
  • 1
    I think you're slightly mistaking Hans' point. Units-of-measure types are not super easy to create in C#; they exist in F# because Andrew Kennedy did a huge amount of design and implementation work to get them into the F# type system. No one did that work for C#, and it is not trivial work that you can emulate easily. That said, there are things you can do; as you've discovered you can make wrapper structs or classes, and there is a lot of boring boilerplate code to write. I would write structs, not classes, but the path you're on is pretty much how you do it in a language without unit types. – Eric Lippert Oct 25 '18 at 16:44
  • 1
    If you need to do this on a shoe-string budget then apps hungarian is notable as a standard solution in languages that don't permit easily overriding value type behavior. When you see yourself adding a kgFoo to a chocBar then you instantly know you have a bug. Not for everybody, but Eric was a big fan :) – Hans Passant Oct 25 '18 at 17:36
  • 2
    Indeed, if you cannot get the compiler to do it, then making typing errors apparent to humans reading the code is better than nothing. Hans is correct to point out that I am a fan of Hungarian notations which encode *semantic* information, like "this is a count of bytes" vs "this is a count of characters" vs "this is an index into a string", and so on. "System Hungarian" where you encode *what is already encoded in the type system* into the name is a pointless waste of effort that I don't understand why it became so popular. – Eric Lippert Oct 25 '18 at 18:35
  • All that said: your primary objection seems to be the amount of time you spend re-implementing operators, but who cares? Just cut-and-paste the boilerplate code. It's only a few dozen lines of code with small changes, and the jitter will inline them away. You'll write the code once and then never think about it again. – Eric Lippert Oct 25 '18 at 18:38
  • 1
    And finally: don't use `float`. Use `double`. Using `float` buys you basically nothing, unless you have hundreds of thousands of them in an array. The math will all be done in doubles on the chip anyways. – Eric Lippert Oct 25 '18 at 18:39
  • Possible duplicate of [Units of measure in C# - almost](https://stackoverflow.com/questions/348853/units-of-measure-in-c-sharp-almost) – Brian Oct 26 '18 at 13:34

0 Answers0