I use this very idea to create a sort of internal grammar for a project which deals with physical measures. I was dubious of this approach at first, but I really like it now, since it makes the source code very readable and easy and fun to write. Here's an example:
A Unit Type:
public struct Celsius : IEquatable<Celsius>
{
private readonly Double _value;
public const string Abbreviation = "°C";
public Celsius(Double value)
{
_value = value;
}
public Boolean Equals(Celsius other)
{
return _value == other._value;
}
public override Boolean Equals(Object other)
{
if (!(other is Celsius))
{
return false;
}
return Equals((Celsius)other);
}
public override int GetHashCode()
{
return _value.GetHashCode();
}
public override string ToString()
{
return _value + Abbreviation;
}
public static explicit operator Celsius(Double value)
{
return new Celsius(value);
}
public static explicit operator Double(Celsius value)
{
return value._value;
}
public static Boolean operator >(Celsius l, Celsius r)
{
return l._value > r._value;
}
public static bool operator <(Celsius l, Celsius r)
{
return l._value < r._value;
}
public static Boolean operator >=(Celsius l, Celsius r)
{
return l._value >= r._value;
}
public static bool operator <=(Celsius l, Celsius r)
{
return l._value <= r._value;
}
public static Boolean operator ==(Celsius l, Celsius r)
{
return l._value == r._value;
}
public static bool operator !=(Celsius l, Celsius r)
{
return l._value != r._value;
}
}
Unit Extensions Class:
public static class UnitsExtensions
{
public static Celsius Celsius(this Double value)
{
return new Celsius(value);
}
public static Celsius Celsius(this Single value)
{
return new Celsius(value);
}
public static Celsius Celsius(this Int32 value)
{
return new Celsius(value);
}
public static Celsius Celsius(this Decimal value)
{
return new Celsius((Double)value);
}
public static Celsius? Celsius(this Decimal? value)
{
return value == null ? default(Celsius?) : new Celsius((Double)value);
}
}
Usage:
var temp = (Celsius)value;
if (temp <= 0.Celsius())
{
Console.Writeline("It's cold!");
}
else if (temp < 20.Celsius())
{
Console.Writeline("Chilly...");
}
else if (temp < 30.Celsius())
{
Console.Writeline("It's quite lovely");
}
else
{
Console.Writeline("It's hot!");
}
I have a number of these types for various measures, like Millimeter
, Radians
, Degrees
, MillimetersPerSecond
, etc. I've even gone so far as to implement division so that when I divide, say, MillimetersPerSecond
by Millimeters
, I get a TimeSpan
value in return. Perhaps this is overboard, but I've found the type safety and the mental ease of using the types worth the effort of implementing and maintaining them.