我设想能够编写流畅的代码,为代码库中的数字增加意义。假设您想要一个数字来表示以英里为单位的距离。你有类似的东西:
用法:
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
}
这样的想法会有用吗,还是在炎热的星期五太晚了?
编辑:是的,没有扩展属性看起来不那么整洁......为匆忙无效代码道歉。这只是一个想法。
答案 0 :(得分:4)
您的代码magic numbers不应该有for good reason,并且编写扩展方法对缓解此问题没有太大作用。你仍然有一个神奇的数字浮动。
如果它是常量,请将其设为常量,并在常量名称中包含_ 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
}
答案 1 :(得分:4)
这是一个有趣的想法,但在做这样的事情之前我会要求一个非常强大的用例。首先,一旦将数字转换为“英里”,您就不能再将其视为int。您要么必须实现整个运算符范围,要么在对它们进行算术运算之前将数英里转换回整数。如果没有充分的理由去做这项工作会有很多额外的工作。
但在某些情况下,这将是一个很好的策略。 我想我曾经记得听过几百万美元的火箭或者曾经{/ 3}}失败的美国国家航空航天局因为程序员将错误的测量单位传递给一个函数。这可以帮助您避免这个问题。
在相关的说明中,您可能对F#感兴趣,它有lost a $125 million space ship。
答案 2 :(得分:2)
您的Miles
结构应该是不可变的。
将其更改为
public struct Miles {
public Miles(int count) : this() { Count = count; } //optionally perform bounds checking
public int Count { get; private set; }
}
答案 3 :(得分:2)
一条评论:使Miles
可变的重点是什么? int
是不可变的,为什么一旦它有一个单位就让它变得可变?
(另外,在C#4中引入了扩展属性?否则这将不起作用。)
最后,如果你想添加单位,它们应该是可组合的,我目前看不到如何实现这一点。
例如,以下代码应该编译:
var x = 100.km;
var y = 10.sec;
var kmh = x / y; // What type does kmh have?
在C ++中,有一个库通过将类型表示为所有七个基本物理单元的维度的元组来实现这一点,但这在C#中不起作用,因为它需要整数作为模板参数。
答案 4 :(得分:2)
我使用这个想法为一个处理物理测量的项目创建一种内部语法。我一开始对这种方法表示怀疑,但我现在非常喜欢它,因为它使源代码非常易读且易于编写。这是一个例子:
单位类型:
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
值作为回报。也许这是过分的,但我发现使用这些类型的类型安全性和心理易用性值得我们努力实现和维护它们。
答案 5 :(得分:1)
以下是您的设计应该如何。
请注意,我们在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;
}
}
答案 6 :(得分:1)
我从之前的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);
}
}
答案 7 :(得分:0)
就个人而言,我没有看到一点。
我认为myMethod的签名不应该是:
public object MyMethod(int miles)
{
// bounds checking on int here
// then logic
}
您还可以使用代码约定使事情更加明确。
添加对.Miles()的调用并使int Mutable更令人困惑。
答案 8 :(得分:0)
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;
}
}
}