views:

197

answers:

9

I envisage the ability to write fluent code that adds meaning to numbers within codebases. Say you wanted a number to represent a distance in miles. You'd have something like:

Usage:

var result = myMethod(100.Miles());

I think this would be much more readable than simply passing in the int, plus you could presumably apply bounds checking to the Miles type.

Extension method and struct implementation:

static class IntExtensions
{
  public static Miles(this int i) { get { return new Miles { Count = i }; } }
}

public struct Miles
{ 
  public int Count { get; private set; } //optionally perform bounds checking
} 

Would such an idea be useful, or is it too late on a hot Friday?

Edit: Yes, doesn't look quite so neat without extension properties... Apologies for the rushed invalid code. It was just an idea.

+2  A: 

Your Miles struct should be immutable.

Change it to

public struct Miles { 
    public Miles(int count) : this() { Count = count; } //optionally perform bounds checking

    public int Count { get; private set; } 
} 
SLaks
+2  A: 

One comment: what’s the point of making Miles mutable? An int isn’t mutable, why make it mutable once it has a unit?

(Additionaly, have extension properties be introduced in C# 4? Otherwise this won’t work.)

Finally, if you want to add units, they should be made composable, and I currently don’t see how to achieve this.

For example, the following code should compile:

var x = 100.km;
var y = 10.sec;
var kmh = x / y; // What type does kmh have?

In C++, there’s a library that implements this by representing the types as tuples of the dimensions of all seven fundamental physical units, but this doesn’t work in C# since it requires integers as template arguments.

Konrad Rudolph
A: 

Personally, I'm not seeing a point.

I see no reason the signature of myMethod shouldn't be:

public object MyMethod(int miles)
{
    // bounds checking on int here
    // then logic 
}

You could also use Code Contracts to make things even more explicit.

Adding a call to .Miles() and making the int Mutable is more confusing.

Justin Niessner
“You could also use Code Contracts to make things even more explicit.” But using types instead of contracts is more explicit still and gives better compiler support. If you regard the type system as a proof system that allows you to formulate assertions and invariants about your code (and really, that’s the *definition* of a type system) then adding units makes a lot of sense. Apart from the fact that code contracts on the callee site require more code duplication by far.
Konrad Rudolph
@Konrad Rudolph - When I suggested Code Contracts, I was referring to the bounds checking logic and validation. Using Code Contracts would allow him to better define the range of expected values and allow the compiler to warn the developers when their values may be out of range. I agree that a Type is more explicit, but in this case it's a simple wrapper around Int32 which doesn't add value in my opinion.
Justin Niessner
@Justin: “it’s a simple wrapper … which doesn’t add value” betrays a misunderstanding of what types are there for. Types don’t have to bundle data, or add a lot of action. The mere fact that *different* types exist is an important purpose of types. Why else are there different units in physics? Any physicist will tell you that when you do a calculation (on paper or in the computer) and the units at the end agree, the calculation is most probably also correct. The same argument applies to fact checking (= compiling with strong types). Consider: why else does the `TimeSpan` class exist?
Konrad Rudolph
@Konrad Rudolph - I definitely see your point. I'm still not positive I agree with it in this case. Oh...and the TimeSpan object adds a ton of useful methods to the value as well as representing the timespan in several different ways. Maybe not the best example.
Justin Niessner
+1  A: 

Here's how your design should be.

Note that, we don't have extension properties in C# yet, it is only extension methods.

class Program
{
    static void Main(string[] args)
    {
        var result = myMethod(100.ToMiles());
        //Miles miles = 100.ToMiles();
    }        
}

static class IntExtensions
{
    public static Miles ToMiles(this int miles)
    {
        return new Miles(miles);
    }
}

struct Miles
{
    public int Count { get; private set; }

    public Miles(int count)
        : this()
    {
        if (count < 0)
        {
            throw new ArgumentException("miles type cannot hold negative values.");
        }
        this.Count = count;
    }
}
this. __curious_geek
Why the redundant prefix `To`? `100.Miles()` will be perfectly clear and unambiguous.
Konrad Rudolph
The method should designate a name that conveys its purpose. ToMiles() conveys the message that this method converts `int` to Miles. Just like any .Net type, int.ToString() converts int value to its string representation.
this. __curious_geek
I agree with the `.ToMiles()` concept. It conforms to other methods, such as `ToString()` or the various methods of `Convert`.
Anthony Pegram
.ToMiles will only work if you can assume that the starting value is always a fixed unit of measure. E.g. km. If it can be any arbitrary distance then ToMiles won't make sense.
Paul Sasik
I disagree somewhat on the 'To' prefix for two reasons: it makes it harder to read the code, and the conversion to string is of a different nature than the number-to-number conversion, since to return from the string is a parsing operation instead of a conversion operation.
codekaizen
Aren't we deviating from the question and boiling down are discussion to some other context ? I have clearly mentioned my idea behind calling it ToMethod() as it perfectly conveys what the method is meant to do. Creating symmetry with .Net .ToString() method was not all my concern.
this. __curious_geek
@geek: I think 'To' isn't perfect; it's superfluous. I added my supporting logic as to why.
codekaizen
@this: Let me quote [PEP 8](http://www.python.org/dev/peps/pep-0008/), the Python style guide: “a foolish consistency is the hobgoblin of little minds” – A new, unprecedented API doesn’t have to follow conventions that obviously don’t apply since they have been developed for a different purpose. FWIW, I think even `AsMiles` would convey the meaning better than `ToMiles`. We’re emulating a new kind of literal here, not a conversion – the conversion is just an implementation detail. And no, I don’t think this discussion is deviating from the question: it concerns an important part of API design.
Konrad Rudolph
everyone have their own opinions. personally I always designate the method with a name that contveys its purpose note tht consistency was not my primary purpose though I'd keep tht also in mind while designing. @konard: int to miles, yes it is conversion. when you say AsMiles(), it means int to miles is a compatible cast.
this. __curious_geek
+4  A: 

You shouldn't have magic numbers in your code, for good reason, and writing an extension method does not do much to alleviate the issue. You still have a magic number floating around.

If it's constant, make it a constant and include the _ MILES _ in the constant's name.

Also, why not wrap the value in a class or struct called Distance that simply contains a numeric value and also a enum that specifies the unit of measure?

Something like:

public class Distance {
    private double _distanceValue;
    private UnitOfMeasure _uom;

    public double DistanceValue {
        get { return _distanceValue; }
        set { _distanceValue = value; }
    }

        public UnitOfMeasure Uom {
        get { return _uom; }
        set { _uom = value; }
    }
}

public enum UnitOfMeasure {
    Kilometers,
    Miles,
    Feet,
    Inches,
    Parsecs
}
Paul Sasik
While I'm not a fan of magic numbers, I think that there is a case where this makes sense, and that is when using this pattern to create a strategy in a strategy pattern. In the domain where I use it, it is important to be able to create new strategies in code quickly and plug them into the process framework. Since the strategy embodies domain logic, it is natural to put the value needed to implement the strategy in the code. This approach makes this very easy to write and more importantly, read.
codekaizen
This reads pretty well to me:var result = myMethod(new Distance(100, UnitOfMeasure.Miles));Plus, imagine if your Distance class had a ConvertTo(UnitOfMeasure) method, and better yet, it was dictated by a base class and inherited by Mass, Weight etc. classes.
Paul Sasik
“why not wrap the value in a class or struct called Distance …” because then you lose any compile-time checking.
Konrad Rudolph
@Konrad - This is right, and is one of the greatest benefits I've found of distinguishing them as separate types, even when I have to treat the types dynamically (e.g. dbReader.GetDouble(0).Celsius()) instead of statically (e.g. 21.3.Celsius()).
codekaizen
A: 
public static class Int32Extensions
{
    public static Miles ToMiles( this Int32 distance )
    {
        return new Miles( distance );
    }
}

public class Miles
{
    private Int32 _distance;

    public Miles( Int32 distance )
    {
        _distance = distance;
    }

    public Int32 Distance
    {
        get
        {
            return _distance;
        }
    }
}
Jerod Houghtelling
A factory method on the Miles class/struct might also be an easy and readable solution. I don't know if in your case it's obvious but it might be good to know if you are creating Miles from a distance measured in feet, yards, etc. If you did the factory methods you could have overloads like `Miles.CreateFromFeet(...)` `Miles.CreateFromYards(...)`, etc.
Jerod Houghtelling
+2  A: 

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.

codekaizen
You certainly don't live in Norway :) 0 == "It's quite lovely", 20 == "It's hot!", 30 == "It's unbearable!", > 30 == "Yeah right!"
simendsjo
I understand it can be very useful when working with the same "units"; 10.Celsius() - 10.Fahrenheit(), but it would be nice to be able to mix units like 10.Km() / 2.Seconds() and get 5 km/s
simendsjo
@simendsjo - I sort of did that with the MillimetersPerSecond / Millimeters => TimeSpan. It works fairly well. It would work better if I could assign an "extension operator" to TimeSpan to allow Millimeters/TimeSpan = MillimetersPerSecond.
codekaizen
+3  A: 

It's an interesting idea, but I would ask for a really strong use case before doing something like this. For one thing, once a number is cast as a "mile", you can no longer treat it as an int. You either have to implement the whole gamut of operators, or cast the miles back to integers before performing arithmetic on them. It's a lot of extra work if there's no good reason to do it.

There are some cases where this would be a good strategy, though. I think I remember hearing about a multi-million-dollar rocket or something that failed NASA once lost a $125 million space ship because programmers were passing the wrong units of measure into a function. This would help you to avoid that problem.

On a related note, you might be interested in F#, which has built-in support for units of measure.

StriplingWarrior
A: 

I grabbed this (with very minor tweaks) from a previous SO question. I much prefer this style, since it falls in line with the common approaches of like DateTime and TimeSpan.

[StructLayout(LayoutKind.Sequential), ComVisible(true)]
    public struct Distance : IEquatable<Distance>, IComparable<Distance>
    {
        private const double MetersPerKilometer = 1000.0;
        private const double CentimetersPerMeter = 100.0;
        private const double CentimetersPerInch = 2.54;
        private const double InchesPerFoot = 12.0;
        private const double FeetPerYard = 3.0;
        private const double FeetPerMile = 5280.0;
        private const double FeetPerMeter = CentimetersPerMeter / (CentimetersPerInch * InchesPerFoot);
        private const double InchesPerMeter = CentimetersPerMeter / CentimetersPerInch;

        public static readonly Distance Zero = new Distance(0.0);

        private readonly double meters;

        /// <summary>
        /// Initializes a new Distance to the specified number of meters.
        /// </summary>
        /// <param name="meters"></param>
        public Distance(double meters)
        {
            this.meters = meters;
        }

        /// <summary>
        /// Gets the value of the current Distance structure expressed in whole and fractional kilometers. 
        /// </summary>
        public double TotalKilometers
        {
            get
            {
                return meters / MetersPerKilometer;
            }
        }

        /// <summary>
        /// Gets the value of the current Distance structure expressed in whole and fractional meters. 
        /// </summary>
        public double TotalMeters
        {
            get
            {
                return meters;
            }
        }

        /// <summary>
        /// Gets the value of the current Distance structure expressed in whole and fractional centimeters. 
        /// </summary>
        public double TotalCentimeters
        {
            get
            {
                return meters * CentimetersPerMeter;
            }
        }

        /// <summary>
        /// Gets the value of the current Distance structure expressed in whole and fractional yards. 
        /// </summary>
        public double TotalYards
        {
            get
            {
                return meters * FeetPerMeter / FeetPerYard;
            }
        }

        /// <summary>
        /// Gets the value of the current Distance structure expressed in whole and fractional feet. 
        /// </summary>
        public double TotalFeet
        {
            get
            {
                return meters * FeetPerMeter;
            }
        }

        /// <summary>
        /// Gets the value of the current Distance structure expressed in whole and fractional inches. 
        /// </summary>
        public double TotalInches
        {
            get
            {
                return meters * InchesPerMeter;
            }
        }

        /// <summary>
        /// Gets the value of the current Distance structure expressed in whole and fractional miles. 
        /// </summary>
        public double TotalMiles
        {
            get
            {
                return meters * FeetPerMeter / FeetPerMile;
            }
        }

        /// <summary>
        /// Returns a Distance that represents a specified number of kilometers.
        /// </summary>
        /// <param name="value">A number of kilometers.</param>
        /// <returns></returns>
        public static Distance FromKilometers(double value)
        {
            return new Distance(value * MetersPerKilometer);
        }

        /// <summary>
        /// Returns a Distance that represents a specified number of meters.
        /// </summary>
        /// <param name="value">A number of meters.</param>
        /// <returns></returns>
        public static Distance FromMeters(double value)
        {
            return new Distance(value);
        }

        /// <summary>
        /// Returns a Distance that represents a specified number of centimeters.
        /// </summary>
        /// <param name="value">A number of centimeters.</param>
        /// <returns></returns>
        public static Distance FromCentimeters(double value)
        {
            return new Distance(value / CentimetersPerMeter);
        }

        /// <summary>
        /// Returns a Distance that represents a specified number of yards.
        /// </summary>
        /// <param name="value">A number of yards.</param>
        /// <returns></returns>
        public static Distance FromYards(double value)
        {
            return new Distance(value * FeetPerYard / FeetPerMeter);
        }

        /// <summary>
        /// Returns a Distance that represents a specified number of feet.
        /// </summary>
        /// <param name="value">A number of feet.</param>
        /// <returns></returns>
        public static Distance FromFeet(double value)
        {
            return new Distance(value / FeetPerMeter);
        }

        /// <summary>
        /// Returns a Distance that represents a specified number of inches.
        /// </summary>
        /// <param name="value">A number of inches.</param>
        /// <returns></returns>
        public static Distance FromInches(double value)
        {
            return new Distance(value / InchesPerMeter);
        }

        /// <summary>
        /// Returns a Distance that represents a specified number of miles.
        /// </summary>
        /// <param name="value">A number of miles.</param>
        /// <returns></returns>
        public static Distance FromMiles(double value)
        {
            return new Distance(value * FeetPerMile / FeetPerMeter);
        }

        public static bool operator ==(Distance a, Distance b)
        {
            return (a.meters == b.meters);
        }

        public static bool operator !=(Distance a, Distance b)
        {
            return (a.meters != b.meters);
        }

        public static bool operator >(Distance a, Distance b)
        {
            return (a.meters > b.meters);
        }

        public static bool operator >=(Distance a, Distance b)
        {
            return (a.meters >= b.meters);
        }

        public static bool operator <(Distance a, Distance b)
        {
            return (a.meters < b.meters);
        }

        public static bool operator <=(Distance a, Distance b)
        {
            return (a.meters <= b.meters);
        }

        public static Distance operator +(Distance a, Distance b)
        {
            return new Distance(a.meters + b.meters);
        }

        public static Distance operator -(Distance a, Distance b)
        {
            return new Distance(a.meters - b.meters);
        }

        public static Distance operator -(Distance a)
        {
            return new Distance(-a.meters);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Distance))
                return false;

            return Equals((Distance)obj);
        }

        public bool Equals(Distance value)
        {
            return this.meters == value.meters;
        }

        public int CompareTo(Distance value)
        {
            return this.meters.CompareTo(value.meters);
        }

        public override int GetHashCode()
        {
            return meters.GetHashCode();
        }

        public override string ToString()
        {
            return string.Format("{0} meters", TotalMeters);
        }
    }
Jim B-G