views:

86

answers:

3

I'm working developing a system right now that deals with lots of conversions between semantically different values that have the same primitive .NET type (double/string/int). This means that it's possible to get confused about which 'semantic type' you are using, either by not converting or converting too many times. Ideally I'd like the compiler to issue a warning/error if I try to use a value where it doesn't semantically make sense.

Some examples to indicate what I'm referring to:

  • Angles may be in units of degrees or radians, yet both are represented by double.
  • Vector positions may be in local/global coordinates, yet both are represented by a Vector3D struct.
  • Imagine a SQL library that accepts various query parameters as strings. It'd be good to have a way of enforcing that only clean strings were allowed to be passed in at runtime, and the only way to get a clean string was to pass through some SQL injection attack preventing logic.

I believe F# has a compile-time solution for this (called units of measure.) I'd like to do something similar in C#, although I don't need the dimensional analysis that units of measure in F# offers.

I believe C++ could achieve this using typedef (though I'm not a C++ expert).

The obvious solution is to wrap the double/string/whatever in a new type to give it the type information the compiler needs. I'm curious if anyone has an alternative solution. If you do think wrapping is the only/best way, then please go into some of the downsides of the pattern (and any upsides I haven't mentioned too.) I'm especially concerned about the performance of abstracted primitive numeric types on my calculations at runtime, so whatever solution I come up with must be lightweight both in terms of memory allocation and call dispatch.

+3  A: 

I am really interested how does compiler give warning when you mix radians and degrees. Are they both double? You are in OOP world, so you should walk this way. Two suggestions:

  1. Use only one unit internally, i think radians is better. Then convert only on input/output.\
  2. Create structs Degree, Radian and define rules of conversions. Or create class 'Angle' and hold all info about units and conversions there.
Andrey
1) I can't use only one type internally, unfortunately. At any rate, what I'm trying to achieve is a compile-time way of enforcing this so as to avoid mistakes. 2) This is pretty much what I described as the obvious way. What I'd really like to know is what people have done about this in the wild, and what kinds of performance impact it had, and any other considerations I should take into account that I might not have thought of. The math is really the bread and butter of the robot I'm coding, so it has to be fast otherwise at worst it'll fall over, or at best eat into strategy cycles.
Drew Noakes
@Drew Noakes, check link in first comment, i think it is rather useful
Andrey
I did have a look at that link. The solutions on there are too costly for what I'm trying to do. I don't want to make a virtual call just to access a numeric value. I've been thinking about this some more. I generally agree with not fighting against OO, but I'm talking about primitive values. If the OO abstraction worked so well for numbers -- ultimately CPU-level data -- then why would we have structs in .NET at all? Inheritance and virtual calls aren't available for them. The only solution I can think of is composition: wrapping a number in another struct. Feels more like C than OOP.
Drew Noakes
I ended up with an `Angle` class that had a single `double` that holds the angle in radians. It has static factory methods `Angle.FromDegrees(180)` and `Angle.FromRadian(Math.PI)`. As an aside, I found that putting `Sin`, `Cos` and `Tan` properties on the type made my code much easier to read as well. I overrode all the relevant operators to make it simple to work with. I also made an complementary type `AngularSpeed` that stores change in angle over time, and made operator overloads that use `TimeSpan` as well, to convert between these types. Seems to perform well and the code looks ok.
Drew Noakes
@Drew Noakes: OOP vs FP : 1 - 0
Andrey
A: 

I've never found a satisfactory solution to this kind of problem in C#. It just seem that the type system wasn't designed for this use case. If I had the same need now, I'd look into the F# units of measure approach.

Scott Weinstein
Clearly it's not capable of this kind of thing today. F# uses logic in the compiler, and I don't think writing my own C# compiler is feasible, or is the current compiler extensible. I also don't want to rewrite the whole thing in F#, hence this question.
Drew Noakes
+2  A: 

I guess you could create two different structs to enforce type checking. In the following code I have added an implicit cast from radians to double and an explicit cast from degress to radians. You could use whatever set of implicit and explicit operators you like, but I think the ones I have defined here would work well since the Radians struct could be passed directly into the Math functions.

public struct Degrees
{
  private double m_Value;

  public static explicit operator Radians(Degrees rhs)
  {
    return rhs.m_Value * (Math.Pi / 180);
  }
}

public struct Radians
{
  private double m_Value;

  public static implicit operator double(Radians rhs)
  {
    return rhs.m_Value;    
  }
}
Brian Gideon
This is one option I was considering, though I think I'd make all conversions explicit (in my case) to avoid accidents, and require explicit construction of either type: `elbow.MoveTo(new Degrees(180));` Do you think there'd be much of a performance hit in wrapping a `double` like this? At the leaves of the computation I'd need to dereference a `Value` property. I'm hoping that somewhere, deep in the JIT, that this basically gets converted away to be equivalent to just using a `double` directly, but I can't quite picture it in my head.
Drew Noakes
Regarding the performance...that is good question. I was thinking about that as well. As long as you keep the method and property accessors short the JIT compiler should inline the calls. I don't think you'll notice much of a difference, but it is definitely something to consider.
Brian Gideon