Motivation:
In reading Mark Seemann’s blog on Code Smell: Automatic Property he says near the end:
The bottom line is that au
DebuggerDisplay
is useful touch. I'd add unit of measurements "{m_value} C" so you can immediately see the type.
Depending on target usage you may also want to have generic conversion framework to/from base units in addtion to concrete classes. I.e. store values in SI units, but be able to display/edit based on culture like (degrees C, km, kg) vs. (degrees F, mi, lb).
You may also check out F# measurement units for additioanl ideas ( http://msdn.microsoft.com/en-us/library/dd233243.aspx ) - note that it is compile time construct.
Is there a way to make MinValue/MaxValue const instead of readonly?
No. However, the BCL doesn't do this, either. For example, DateTime.MinValue is static readonly
. Your current approach, for MinValue
and MaxValue
is appropriate.
As for your other two questions - usability and the pattern itself.
Personally, I would avoid the automatic conversions (implicit conversion operators) for a "temperature" type like this. A temperature is not an integer value (in fact, if you were going to do this, I would argue that it should be floating point - 93.2 degrees C is perfectly valid.) Treating a temperature as an integer, and especially treating any integer value implicitly as a temperature seems inappropriate and a potential cause of bugs.
I find that structs with implicit conversion often cause more usability problems than they address. Forcing a user to write:
Celsius c = new Celcius(41);
Is not really much more difficult than implicitly converting from an integer. It is far more clear, however.
I think this is a perfectly fine implementation pattern for value types. I've done similar things in the past that have worked out well.
Just one thing, since Celsius
is implicitly convertible to/from int
anyway, you can define the bounds like this:
public const int MinValue = -273;
public const int MaxValue = int.MaxValue;
However, in reality there's no practical difference between static readonly
and const
.
I think from a usability point of view I would opt for a type Temperature
rather than Celsius
. Celsius
is just a unit of measure while a Temperature
would represent an actual measurement. Then your type could support multiple units like Celsius, Fahrenheit and Kelvin. I would also opt for decimal as backing storage.
Something along these lines:
public struct Temperature
{
private decimal m_value;
private const decimal CelsiusToKelvinOffset = 273.15m;
public static readonly Temperature MinValue = Temperature.FromKelvin(0);
public static readonly Temperature MaxValue = Temperature.FromKelvin(Decimal.MaxValue);
public decimal Celsius
{
get { return m_value - CelsiusToKelvinOffset; }
}
public decimal Kelvin
{
get { return m_value; }
}
private Temperature(decimal temp)
{
if (temp < Temperature.MinValue.Kelvin)
throw new ArgumentOutOfRangeException("temp", "Value {0} is less than Temperature.MinValue ({1})", temp, Temperature.MinValue);
if (temp > Temperature.MaxValue.Kelvin)
throw new ArgumentOutOfRangeException("temp", "Value {0} is greater than Temperature.MaxValue ({1})", temp, Temperature.MaxValue);
m_value = temp;
}
public static Temperature FromKelvin(decimal temp)
{
return new Temperature(temp);
}
public static Temperature FromCelsius(decimal temp)
{
return new Temperature(temp + CelsiusToKelvinOffset);
}
....
}
I would avoid the implicit conversion as Reed states it makes things less obvious. However I would overload operators (<, >, ==, +, -, *, /) as in this case it would make sense to perform these kind of operations. And who knows, in some future version of .net we might even be able to specify operator constraints and finally be able to write more reusable data structures (imagine a statistics class which can calculate statistics for any type which supports +, -, *, /).