Introduction
One of the typical localization issues when developing applications is that of units conversion – the conversion of one measurement system to another. Though the mathematics involved in such conversions is, in most cases, very simple, the semantics surrounding the process of changing units is less so. This article is a look at the typical problem faced by developers when implementing support for units conversion in C# applications. In it, we hope to explore the many caveats and problems typically encountered by developers.
The problem
The typical 'divide' in terms of units comes in the differences between U.S. and European measurement systems. For example, in U.S., distance is measured in miles; in continental Europe, it is measured in Kilometers. Temperature in the US is measured in Fahrenheit, whereas we Europeans prefer degrees Celsius. Other engineering units have similar distinctions, although they might not always be following the US/Europe divide – sometimes there are simply several culture-neutral notations for measuring a quantity of something.
Let's take temperature as an example. There are two measurement units we shall consider – Celsius (°C) and Fahrenheit (°F). There are others, but let's ignore them for now. The conversion function (link) is specified as follows:
°C = 5/9 × (°F - 32)
To convert Fahrenheit to Celsius, we subtract 32 and multiply by 5/9, or 0.5(5). The opposite conversion, i.e., conversion from Celsius to Fahrenheit, would look as follows:
°F = (°C / (5/9)) + 32 = 1.8 × °C + 32
I have specifically rewritten the formula so that it appears in Ax+B form, where A and B are the two parameters that define one unit in respect to another. In fact, this form of definition is suitable for the vast majority of numeric conversions. I am going to refer to A as the scale factor of the conversion, and B as the shift. Now that we know what conversions typically look like (not that hard, is it?), let's outline the problem. We want to:
- Perform engineering calculations
- Have a simple API for units conversion
- Let the user choose their own units
- Let the user view and edit data in their chosen units
Defining a unit
When defining a set of measurement units, the first thing that springs to mind is that there is typically a 'reference' measurement unit – i.e., a unit against which all other units have scale factor and shift. Since I live in Europe, I choose Celsius to be the base temperature unit, i.e., a unit with a scale factor of 1.0 and a shift of 0.0. Relative to degrees Celsius, the Fahrenheit unit has a scale factor of 1.8 and a shift of +32. In addition to this data, I would also want to keep the name of the unit of measurement (e.g., "Degrees Fahrenheit") and its abbreviation ("°F"). Having this data attached to the unit makes creating the UI a bit easier.
The four pieces of information I just mentioned (Name, Abbreviation, Scale Factor, and Shift) define our unit. However, we have neglected one additional piece of information – what the unit actually measures. Consequently, our fifth field is a reference to a UnitType
enumeration which serves no purpose other than to delineate our units based on what they actually measure. Here is what the final unit structure looks like:
internal struct Unit
{
internal string Name;
internal string Abbreviation;
internal double Scale;
internal double Shift;
internal UnitType Type;
internal Unit(string name, string abbreviation,
double scale, double shift, UnitType type)
{
if (scale == 0.0)
throw new ArgumentException("Scale factor cannot be zero.");
Name = name;
Abbreviation = abbreviation;
Scale = scale;
Shift = shift;
Type = type;
}
public override string ToString()
{
return string.Format("{0} ({1})",
Name, Abbreviation);
}
}
I have tried to keep things as simple as possible by having the unit as a struct
, and having just a single constructor. I could have used properties to make the structure immutable, but I decided that various OOP features will cost too much here – especially when support for hundreds of measurement units is required. There isn't much to note about this structure except, perhaps, two things. First, the constructor throws an exception if the scale factor is zero – this helps to prevent a potential division by zero attempt later on. Second, the struct
overrides the ToString()
method. Since I'm using the Unit
type in UI controls, having a textual representation via the ToString()
overload is the simplest way to go.
Units
Now that we have decided upon the structure of a single unit, let's make a 'class to rule them all' which we shall (imaginatively) call Units
. This class will store all units, and will allow us to do conversion from one unit to another. There will typically be one of these Units
classes in an application. Therefore, we shall restrict this class to just one instance. Our situation is, actually, a perfect example of the Singleton pattern being a lot better than a static class. The reason for this is that you cannot have change notifications on static members. Ooh, all right, you can get them with some hackery, but those solutions are unmaintainable and generally painful. Oh, and you are probably wondering why we would want to have property change notifications in our Units
class. The reason for this is that property changes need to revalidate the UI (immediately!), which means that our values need to be informed by the Units
class that they need to revalidate.
Let's briefly outline the features of our Units
class that we need to implement:
- Units conversion function
- Data store with the unit definitions
- Conversions to and from internal units (helper functions)
- Storage of user preferences
- Change notifications
Let us now take a look at how some of these features are implemented.
Conversion function
The conversion function is beautiful in its simplicity.
internal static double Convert(Unit from, Unit to, double value)
{
if (from.Type != to.Type)
throw new InvalidOperationException("Conversion between incompatible types.");
double v = (value - from.Shift) / from.Scale;
return v * to.Scale + to.Shift;
}
Now you can see why we check the Scale
parameter in the Unit
constructor – had we not done it, this function would try to divide by zero, which is something we want to prevent.
Preferences
When working with engineering calculations, we have a dichotomy. Unless we want to keep doing unit conversions all the time (could be taxing), we need to keep our data in reference units (whatever they may be) in order to easily use them in engineering calculations. On the other hand, the user has to see an altogether different set of units, depending on what settings they chose. As a result, a measurement value ends up existing in two states – an intrinsic state which uses our reference unit (in my case - degrees Celsius), and an extrinsic state which is computed depending on user preferences. Here, I use the terms System and User instead of intrinsic and extrinsic.
Given the above, we now create two properties which store the temperature units that the system uses for calculations, and the temperature units that the user chooses for presentation.
public Unit TemperatureSystemUnit
{
get { return temperatureSystemUnit; }
set
{
temperatureSystemUnit = value;
Changed("TemperatureSystemUnit");
}
}
public Unit TemperatureUserUnit
{
get { return temperatureUserUnit; }
set
{
temperatureUserUnit = value;
Changed("TemperatureUserUnit");
}
}
As you can see, the system and user temperature settings notify about being changed. This is essential, since UI data needs to be updated as soon as the user changes these preferences.
At the moment, units conversion to and from system units is problematic. Having implemented things the way we did, we have made one critical mistake: we forgot to associate our system/user units with the UnitType
they represent. My approach to this is to 'bite the bullet' and implement the functions that convert to/from internal values, as follows:
internal static double ConvertToSystem(UnitType type, double value)
{
switch (type)
{
case UnitType.Temperature:
return Convert(DegreesUserUnit, DegreesSystemUnit, value);
default:
throw new InvalidOperationException("Unit type not supported.");
}
}
internal static double ConvertFromSystem(UnitType type, double value)
{
switch (type)
{
case UnitType.Temperature:
return Convert(DegreesSystemUnit, DegreesUserUnit, value);
default:
throw new InvalidOperationException("Unit type not supported.");
}
}
There are other possible solutions here, but none that are as fast as this. Having written these two functions, we can now start using them in our business entities when defining system and user properties.
Practical example
Let's try to put the functionality we wrote to use. Suppose we have a class modeling a patient. The patient has a temperature stored internally in Celsius, but the user gets to choose which units to see data in. I am going to use a CLR object to define this entity. Let us start with the private field and the system value.
private double temperature;
public double SystemTemperature
{
get { return temperature; }
set
{
if (temperature != value)
{
temperature = value;
Changed("SystemTemperature");
Changed("UserTemperature");
}
}
}
Two things to say about this code. First, we do an assignment check to make sure we're not causing unnecessary notifications. This is not so much of an issue in WPF, but it was a real pain with WinForms when control-bound properties were updating far too often than was necessary. The other thing to note is that we do property changed notifications (that's what the Changed()
method does) for both system and user properties. This makes sense, since when one changes, the other changes too. Let us now take a look at the user property.
public double UserTemperature
{
get
{
return Units.Instance.ConvertFromSystem(UnitType.Temperature, temperature);
}
set
{
double v = Units.Instance.ConvertToSystem(UnitType.Temperature, value);
if (temperature != v)
{
temperature = v;
Changed("SystemTemperature");
Changed("UserTemperature");
}
}
}
We are finally putting this unit-conversion functionality to good use! You might experience slight discomfort from seeing Units.Instance
everywhere, but bear in mind that it doesn't mess up C# code nearly as much as it messed up data binding expressions in XAML. Just to reiterate, implementing a Singleton is a necessary evil, and you'll thank yourself later.
Here comes the tricky part. Remember that when you change units, there's nothing to tell the controls to update (basically, re-read) their values. This code is actually added in our patient class. The constructor simply subscribes to the PropertyChanged
event. Then, we do the notifications in our Patient
object.
public Patient()
{
Units.Instance.PropertyChanged += UnitsChanged;
}
void UnitsChanged(object sender, PropertyChangedEventArgs e)
{
Changed("SystemTemperature");
Changed("UserTemperature");
}
If you have many different types of units, you would need to refine this code to check the name of the event that was fired on Units
in order to determine which events to fire on Patient
. And yes, I must admit: it is tedious.
Binding to UI
You probably won't be surprised to know that further changes are required to our unit data structures in order to support data binding. Here's an example: I want a combo box with all temperature units in it. Currently, there is no single collection of all temperature units. Since I'm not interested in using Reflection, and units don't change after created, my solution is to simply define a list where all units of a particular type are held:
public static readonly IList TemperatureUnits;
Now that I have the simple public property, I place my unit initializations into the list right inside the constructor. All in one statement!
TemperatureUnits = new[]
{
DegreesCelcius = new Unit("Degrees Celcius", "°C", 1.0, 0.0, UnitType.Temperature),
DegreesFahrenheit = new Unit("Degrees Fahrenheit", "°C", 1.8, 32, UnitType.Temperature)
};
Binding to singletons is a bit ugly. Here is a combo box that uses the above list:
<ComboBox Grid.Column="1" Margin="3" Grid.ColumnSpan="2"
DataContext="{Binding Source={x:Static uc:Units.Instance}}"
ItemsSource="{Binding Path=TemperatureUnits}"
SelectedItem="{Binding Path=TemperatureUserUnit}"/>
The fact that I defined a data context inside a ComboBox
element is in itself a pretty disturbing sign, but there seems to be no other way. That's what I meant when I said that WPF syntax for singletons is uglier than the C# syntax.
Now, on to our patient. Binding to the Patient
data structure is easy. We simply define a text box as follows:
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Grid.ColumnSpan="2">
<TextBox.Text>
<Binding Path="MyPatient.UserTemperature"
Converter="{StaticResource dtsc}"
UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
As you can see, we simply bind to the user property of the entity class. To handle incorrect input, I have created a converter between the double
and string
types, which is refferred to here (as dtsc
). It is defined as follows:
[ValueConversion(typeof(double), typeof(string))]
public class DoubleToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return string.Format("{0:F3}", (double) value);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
double d;
if (double.TryParse((string)value, out d))
return d;
return 0.0;
}
}
Tweaks
There are a couple of tweaks I can think of off the top of my head – these may or may not be applicable to your development situation.
First, unit strings are hardcoded – something not recommended when making applications that work in many countries. This is why we are doing unit conversions, right? So, my approach would be as follows: instead of just one string kept in the Unit
class, you can keep a whole dictionary of units. Then, when returning the units, you could use something like Thread.CurrentThread.CurrentUICulture.Name
as the key to access the right string. This applies to both unit names and abbreviations. If you are displaying unit type names, those have to be localized too.
Some unit conversions are more complicated than Ax + B (or so I'm told). My take on this would be to add a reference to either a delegate or an IValueConverter
which would do the really clever conversion. Of course, complex conversions require more data from the entity class, so it might not be that easy. Until I actually see such a conversion, I couldn't say for sure how to handle it.
Units can have dependencies too! For example, if you measure distance in meters, you probably want to measure speed in meters per second and not miles per hour. These dependencies are actually not so difficult to do – you just have to add the appropriate calls in the property mutators.
Conclusion
Switching units and handling all the changes that come with it is tricky, and the process is not always pretty. However, implementing your own units and unit conversions is possible while preserving full WPF data binding fidelity. And, though we mainly discussed WPF in this article, the approach described works fine with WinForms, too, since the unit-related code does not have any WPF-specific features. I have refrained from using dependency properties and such for a very particular reason – my experience with engineering applications has taught me that in cases of very complex property dependencies (far more complex than those presented here), the user of the INotifyPropertyChanged
interface is the safest approach. This interface is fairly well-known and, of course, WPF has no problem with it.