4
\$\begingroup\$

I have some enums that define measurement information, such as volume, mass, distance etc...

They are defined as such:

/// <summary>
/// Units for measurement of area.
/// </summary>
public enum Area
{
    /// <summary>
    /// Meters squared.
    /// </summary>
    M2 = 0,
    /// <summary>
    /// Feet squared.
    /// </summary>
    FT2
}

I have the need to display a short and long description for the units. A short description of meters in my case would be [m] and the long would simply be Meters. There are other larger enums for things like acceleration that define Kilometers per hour per second/[km/h/s].

What I'd done until now was use the DescriptionAttribute attribute to provide simply the short description:

/// <summary>
/// Distance measurement units
/// </summary>
public enum Distance
{
    /// <summary> 
    /// Meters.
    ///</summary> 
    [Description("[m]")]
    M = 0,        
    ...
}

That has worked for something that had a type of Distance. I've now got things that are defined as integer types that are actually a distance measurement:

public class MovementSnapshot
{
    /// <summary>
    /// The total distance traveled, in meters.
    /// </summary>
    public int TotalTravelDistance { get; private set; }

    /// <summary>
    /// The current speed of the car. In KM/H
    /// </summary>
    public int Speed { get; private set; }

    /// <summary>
    /// The maximum speed achieved. In KM/H
    /// </summary>
    public int MaxSpeed { get; private set; }
}

I defined a custom attribute to make it easier to display these properties when outputting them to file/screen etc... that looks like this:

/// <summary>
/// Provides an interface for the display of Unit-type objects
/// </summary>
public class UnitDisplayAttribute
    : Attribute
{
    /// <summary>
    /// The short display of the unit. Example: [N]
    /// </summary>
    public string ShortDisplay { get; private set; }

    /// <summary>
    /// Long long display of the unit. Example: Newtons
    /// </summary>
    public string LongDisplay { get; private set; }

    /// <summary>
    /// Construct the unit display attribute providing a short and long description of a unit type.
    /// </summary>
    /// <param name="longDisplay"></param>
    /// <param name="shortDisplay"></param>
    public UnitDisplayAttribute(string longDisplay, string shortDisplay)
    {
        this.ShortDisplay = shortDisplay;
        this.LongDisplay = longDisplay;
    }

}

This would work well but I'm now running into this situation when applying the attribute to the class I defined above:

public class MovementSnapshot
{
    /// <summary>
    /// The total distance traveled, in meters.
    /// </summary>
    [UnitDisplay("[m]", "Meters")]
    public int TotalTravelDistance { get; private set; }

    /// <summary>
    /// The current speed of the car. In KM/H
    /// </summary>
    [UnitDisplay("[km/h]", "Kilometers per hour")]
    public int Speed { get; private set; }

    /// <summary>
    /// The maximum speed achieved. In KM/H
    /// </summary>
    [UnitDisplay("[km/h]", "Kilometers per hour")]
    public int MaxSpeed { get; private set; }
}

I have to duplicate the attribute for properties measured in the same units... Some classes have 10-12 of them. I then thought of adding a constructor to the UnitDisplayAttribute class that takes an instance of Distance for example, and could provide a string method to output the display.

/// <summary>
/// Provides an interface for the display of Unit-type objects
/// </summary>
public class UnitDisplayAttribute
    : Attribute
{
    /// <summary>
    /// The short display of the unit. Example: [N]
    /// </summary>
    public string ShortDisplay { get; private set; }

    /// <summary>
    /// Long long display of the unit. Example: Newtons
    /// </summary>
    public string LongDisplay { get; private set; }

    public UnitDisplayAttribute(Distance d)
    {
        // check if the distance is meters, kilometers etc... and apply the appropriate short and long strings to the property.
    }

}

That will work well for my MovementSnapshot class above, but applying the custom attribute to the Distance enum above feels awkward because the attribute takes an object of the same type:

/// <summary>
/// Distance measurement units
/// </summary>
public enum Distance
{
    /// <summary> 
    /// Meters.
    ///</summary> 
    [UnitDisplay(Distance.M)]
    M = 0,        
    ...
}

Am I worrying about nothing here? Will Jimmy lose his mind while looking at this code? How can I achieve a nice display for objects that are represented as a unit and objects that are that unit?

\$\endgroup\$
  • \$\begingroup\$ Should we assume that every property will have a corresponding enum value with the correct unit? e.g. from your MovementSnapshot example, should we assume there's a Speed enum with a value for kilometers per hour? \$\endgroup\$ – Ben Aaronson Feb 27 '15 at 1:10
  • \$\begingroup\$ Yes. I didn't include all of them because there's quite a lot. Mass, Volume, Distance, Acceleration, Pressure etc... \$\endgroup\$ – Brandon Feb 27 '15 at 18:54
4
\$\begingroup\$

I'm going to suggest a different approach entirely. I like OOP.

Create an interface.

public interface IUnitOfMeasure
{
    int Value { get; set; }
    string ShortDescription { get; }
    string LongDescription { get; }
}

And a Formatter Enum

public enum UnitOfMeasureFormat { Short, Long }

And then implement the interface, over riding and overloading the ToString() method.

public class Meters : IUnitOfMeasure
{
    public int Value { get; set; }
    public string ShortDescription { get { return "[m]"; }
    public string LongDescription { get { return "Meters"; }

    public override string ToString()
    {
        return this.Value + " " + this.ShortDescription;
    }

    public string ToString(UnitOfMeasureFormat format)
    {
        if (format == UnitOfMeasureFormat.Short)
        {
            return this.ToString();
        }

        return this.Value + " " + this.LongDescription;
    }
}

And now you can give your class' properties a "stronger" type.

public class MovementSnapshot
{
    public Meters TotalTravelDistance { get; private set; }

Simply calling the ToString() method when you need to display it.

myMovementSnapshot.TotalTravelDistance.ToString();

I didn't do it here, but at this point, you might want to start thinking about implementing unit converter methods as well as implementing the IEquatable and IComparable interfaces.

\$\endgroup\$
  • \$\begingroup\$ I really like this a lot. I have every conceivable unit conversion in place already but they're kind of strewn about. I want to implement it like this but it'll be a major refactoring effort. I think this answer will be correct, but I want to leave the question open to get a few more viewpoints. \$\endgroup\$ – Brandon Feb 27 '15 at 18:58
  • \$\begingroup\$ Yup. No problem. I'm happy I could help. I'm fairly new to C#, so somebody else could certainly have a better solution. Good luck with your refactoring efforts! \$\endgroup\$ – RubberDuck Feb 27 '15 at 19:00
  • \$\begingroup\$ I was looking through the usages of the enum values throughout the application (I inherited the application). They're used to keep track of how the values are being measured... So for example, it's possible that the overall unit of distance measurement is Distance.Meters. So every measurement that represents distance will be an integer that will represent meters. Conversely, it's possible that the same value is actually represented in miles... Unfortunately I don't know if your solution will work in that case as the variable names will not change, but they represent will... \$\endgroup\$ – Brandon Feb 27 '15 at 19:20
  • \$\begingroup\$ Oh. Ouch. Ummm.... yeah. Sounds like you might have bigger issues here. This solution would probably not work without a much larger refactoring in that case. \$\endgroup\$ – RubberDuck Feb 27 '15 at 19:23
  • \$\begingroup\$ Yeaaahh... I mean, it makes sense in the context of my application because it's possible that you may want to specify distance in imperial units but your data source only has metric units for pressure or volume (which happens actually almost every time). Damn... \$\endgroup\$ – Brandon Feb 27 '15 at 19:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.