Дополнительный метод для Int32 в C#

Я предусматриваю способность написать быстрый код, который добавляет значение к числам в кодовых базах. Скажите, что Вы хотели, чтобы число представило расстояние в милях. У Вас было бы что-то как:

Использование:

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

Я думаю, что это было бы намного более читаемо, чем просто передача в интервале, плюс Вы могла, по-видимому, применить проверку границ на тип Miles.

Дополнительный метод и реализация структуры:

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
} 

Такая идея была бы полезна, или является этим слишком поздно в жаркую пятницу?

Править: Да, не выглядит вполне таким образом аккуратным без дополнительных свойств... Извинения за срочно отправленный недопустимый код. Это была просто идея.

7
задан Ben 25 June 2010 в 16:10
поделиться

9 ответов

Это интересная идея, но я бы попросил предоставить действительно убедительный пример использования, прежде чем делать что-то подобное. Во-первых, как только число приводится как "мил", вы больше не можете обращаться с ним как с int. Вам придется либо реализовать весь набор операторов, либо приводить мили обратно к целым числам, прежде чем выполнять над ними арифметические действия. Это много лишней работы, если нет веских причин для ее выполнения.

Хотя есть некоторые случаи, когда это было бы хорошей стратегией. Кажется, я помню, что слышал о ракете стоимостью в несколько миллионов долларов, которая потерпела неудачу NASA однажды потеряло космический корабль стоимостью 125 миллионов долларов из-за того, что программисты передавали в функции неправильные единицы измерения. Это поможет вам избежать подобной проблемы.

В связи с этим вас может заинтересовать F#, в котором есть встроенная поддержка единиц измерения.

4
ответ дан 6 December 2019 в 12:46
поделиться

Вы не должны иметь магические числа в вашем коде, по веской причине, и написание метода расширения не сильно облегчает проблему. У вас все еще есть магическое число.

Если это константа, сделайте ее константой и включите _ MILES _ в имя константы.

Также, почему бы не обернуть значение в класс или структуру под названием Distance, которая просто содержит числовое значение, а также перечисление, определяющее единицу измерения?

Что-то вроде:

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
}
4
ответ дан 6 December 2019 в 12:46
поделиться

Один комментарий: какой смысл делать Майлз изменяемым? int не изменяемый, зачем делать его изменяемым, если у него есть единица измерения?

(Дополнительно, нужно ли вводить свойства расширения в C # 4? В противном случае это не сработает.)

Наконец, , если вы хотите добавить единицы, их следует сделать компонуемыми, и в настоящее время я не понимаю, как этого добиться.

Например, следующий код должен скомпилироваться:

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

В C ++ есть библиотека, которая реализует это, представляя типы в виде кортежей с размерами всех семи основных физических единиц, но это не работает в C #, поскольку он требует целых чисел в качестве аргументов шаблона.

2
ответ дан 6 December 2019 в 12:46
поделиться

Я использую эту идею, чтобы создать своего рода внутреннюю грамматику для проекта, который имеет дело с физическими измерениями. Сначала я сомневался в этом подходе, но сейчас он мне очень нравится, поскольку он делает исходный код очень читабельным, легким и интересным для написания. Вот пример:

Тип единицы измерения:

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

Класс расширения единицы:

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

Использование:

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

У меня есть несколько этих типов для различных мер, например Миллиметр , Радианы. , Градусы , Миллиметры в секунду и т. Д. Я даже реализовал деление, чтобы при делении, скажем, миллиметров на секунду на Миллиметры , я получаю взамен значение TimeSpan . Возможно, это перебор, но я обнаружил, что безопасность типов и умственная простота использования типов стоят усилий по их реализации и поддержке.

2
ответ дан 6 December 2019 в 12:46
поделиться

Ваш Miles struct должен быть неизменяемым.

Измените его на

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

    public int Count { get; private set; } 
} 
2
ответ дан 6 December 2019 в 12:46
поделиться

Вот каким должен быть ваш дизайн.

Обратите внимание, у нас еще нет свойств расширения в C #, это только методы расширения.

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;
    }
}
1
ответ дан 6 December 2019 в 12:46
поделиться

Я взял это (с очень небольшими изменениями) из предыдущего вопроса SO. Я предпочитаю этот стиль, поскольку он соответствует общим подходам, например DateTime и 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);
        }
    }
1
ответ дан 6 December 2019 в 12:46
поделиться

Лично я не вижу в этом смысла.

Я не вижу причин, по которым подпись myMethod не должна быть:

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

Вы также можете использовать Code Contracts, чтобы сделать вещи еще более явными.

Добавление вызова к .Miles () и создание изменяемого int сбивает с толку.

0
ответ дан 6 December 2019 в 12:46
поделиться
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
ответ дан 6 December 2019 в 12:46
поделиться