Click here to Skip to main content
Click here to Skip to main content

Don't Put Meaningful Values in Primitives

, 13 Dec 2012
Rate this:
Please Sign up or sign in to vote.
Don't put meaningful values in primitives

Consider the following simple bit of code:

static void Main()
{
    double currentTemperature = 85; // from external sensor

    if (IsTooHot(currentTemperature))
    {
        ShutDown("Too hot!");
    }
    else
    {
        DoImportantWork();
    }
}

public static bool IsTooHot(double temperature)
{
    const double MaxTemperature = 90.0;

    if (temperature > MaxTemperature)
        return true;
    return false;
}

At first glance it looks fairly inocuous but there's potential for a disaster lurking in there, can you see it? What if we change the names of the temperature variables to currentTemperatureInCelcius and maximumTemperatureInFahrenheit? Uh oh. Think this an unlikely scenario? A similar mixing of measurements caused the loss of a NASA spacecraft. An embarassing and expensive error.

The values in our program aren't just numbers, they are a measurement of something, and that something is important too. Using primitives to represent them loses the essence of what we are dealing with. Recently I've been reading about F#, which has some very interesting features, one of which is being able to give values units of measure, which would prevent the kind of mishap here if used. While we can't do exactly the same in C# we can use the type system to increase the safety of our code. First up lets make a couple of types to represent our measurements:

public struct Celcius
{
    public double Value;

    public Celcius(double value)
    {
        Value = value;
    }
}

public struct Fahrenheit
{
    public double Value;

    public Fahrenheit(double value)
    {
        Value = value;
    }

    public static bool operator > (Fahrenheit val1, Fahrenheit val2)
    {
        return val1.Value > val2.Value;
    }

    public static bool operator <(Fahrenheit val1, Fahrenheit val2)
    {
        return val1.Value < val2.Value;
    }
}

Notice that we are using a struct here not a class, this matches the semantics of the original usage better than a class would, which will become clearer later. For brevity's sake I've only defined greater than and less than operators for Fahrenheit as this is all that's needed for this sample.

Now let's update our original program to use the new types:

static void Main()
{
    Celcius currentTemperature = new Celcius(85);

    if (IsTooHot(currentTemperature))
    {
        ShutDown("Too hot!");
    }
    else
    {
        DoImportantWork();
    }
    Console.ReadLine();
}

public static bool IsTooHot(Fahrenheit temperature)
{
    Fahrenheit MaxTemperature = new Fahrenheit(90.0);

    if (temperature > MaxTemperature)
        return true;
    return false;
}

And it won't compile; we've now got type safety ensuring that we can't mix up our measurements. Now, we could write some kind of static converter and call it every time we need to convert values but that would make things look quite untidy. Another option is to make use of C# implicit conversion operator. Add this to the Celcius type:

public static implicit operator Fahrenheit(Celcius val)
{
    return new Fahrenheit(val.Value * 1.8 + 32);
}

Now we can use Celcius values wherever Fahrenheit ones are expected and the value will be converted for us, maintaining the safety of our operations while reducing the friction of having to manually do conversions every place they are needed. I'd be wary of doing this with classes, where reference type semantics mean there's an expectation that the object will remain the same when doing these kinds of casts, but with value types there is no such expectation so it shouldn't cause any problems.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Pete Sutcliffe
Software Developer (Senior) AJ Systems
United Kingdom United Kingdom
I am currently a developer at a management consultancy company.
In addition to all things .NET I have a keen interest in Japan and the Japanese language and hope to be able to combine these passions one day soon.

Comments and Discussions

 
GeneralBest practices PinmemberSilic0re095-Jan-13 17:01 
Bug(9 / 5) is 1 Pinmemberspringy7612-Dec-12 21:45 
Did you expect 1.8 there?
GeneralRe: (9 / 5) is 1 PinmemberPete Sutcliffe12-Dec-12 22:38 
GeneralRe: (9 / 5) is 1 [modified] PinmemberKP Lee17-Dec-12 11:50 
AnswerRe: (9 / 5) is 1 PinmemberCarlos190712-Dec-12 23:03 
GeneralRe: (9 / 5) is 1 Pinmemberspringy7612-Dec-12 23:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 13 Dec 2012
Article Copyright 2012 by Pete Sutcliffe
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid