开发者

Extension method for Int32 in C#

I envisage the ability to write fluent code that adds meaning to numbers within cod开发者_如何转开发ebases. 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.


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.


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
}


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.


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.


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; } 
} 


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;
    }
}


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);
        }
    }


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.


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;
        }
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜