Метод расширения для Int32 в C # - PullRequest
7 голосов
/ 25 июня 2010

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

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

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

Я думаю, что это было бы намного удобнее для чтения, чем просто передача int, плюс вы, вероятно, могли бы применить проверку границ для типа 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
} 

Будет ли такая идея полезной или уже слишком поздно в жаркую пятницу?

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

Ответы [ 9 ]

4 голосов
/ 25 июня 2010

Это интересная идея, но я бы попросил действительно сильный вариант использования, прежде чем делать что-то подобное. С одной стороны, если число указано как «миля», вы больше не можете рассматривать его как целое число. Вы должны либо реализовать всю гамму операторов, либо привести мили обратно к целым числам, прежде чем выполнять арифметику над ними. Это лишняя работа, если для этого нет веской причины.

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

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

4 голосов
/ 25 июня 2010

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

Если это константа, сделайте ее константой и включите _ 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
}
2 голосов
/ 25 июня 2010

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

A Тип устройства:

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

У меня есть несколько этих типов для различных мер, таких как Millimeter, Radians, Degrees, MillimetersPerSecond и т. Д. Я даже ушелнастолько, чтобы реализовать деление так, чтобы, когда я делю, скажем, MillimetersPerSecond на Millimeters, я получал взамен TimeSpan значение.Возможно, это за бортом, но я нашел безопасность типов и умственную простоту использования типов, стоящих усилий для их внедрения и обслуживания.

2 голосов
/ 25 июня 2010

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

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

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

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

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

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

2 голосов
/ 25 июня 2010

Ваша Miles структура должна быть неизменной.

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

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

    public int Count { get; private set; } 
} 
1 голос
/ 25 июня 2010

Я взял это (с очень незначительными изменениями) из предыдущего вопроса 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 голос
/ 25 июня 2010

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

Обратите внимание: у нас пока нет свойств расширения в 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;
    }
}
0 голосов
/ 25 июня 2010
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 голосов
/ 25 июня 2010

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

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

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

Вы также можете использовать кодовые контракты для создания вещей.еще более явно.

Добавление вызова .Miles () и превращение int в Mutable более запутанно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...