使用枚举实现层次结构的最佳C#模式是什么?

10

我正在实现表示距离(或长度)的值类型。 有一个枚举来表示不同的度量单位,例如:

public enum DistanceUnit 
{
   Millimeter,
   Centimeter,
   Meter,
   Kilometer,
   Inch,
   Foot,
   Yard,
   Mile
};
这些测量值分为两种系统——米制或英制。由于枚举不支持层次结构,那么表示此层次结构的最佳模式是什么?
使用位标志?还是使用两个单独的枚举类型以及一些方法来关联它们?还是声明静态成员而不是枚举类型?
给我一些建议...你会如何实现这个?
编辑-更多澄清: 我有几个不可变结构(由T4生成),代表各种测量单位:
public struct Meters : IEquatable<Distance>, 
                       IEquatable<Meters>, 
                       IEquatable<Millimeters>, ... IComparable<> etc.. etc..
{
    public readonly decimal Value;
    ...
    public static implicit operator Distance (Meters other) {
        // Can implicitly cast to Distance
    }
}

public struct Millimeters ...
public struct Centimeters ....

... 以及一个手写不可变的Distance,旨在表示任何度量:

public struct Distance : IEquatable<Distance>, 
                       IEquatable<Meters>, 
                       IEquatable<Millimeters>, ...
                       IFormattable
{

    public readonly decimal Value;
    public readonly DistanceUnits UnitOfMeasure;
    ...
    public string ToString(string format, IFormatProvider provider)
    {
        // Logic:
        // If UOM = Meters and Value < .1 then display as "10 cm" instead of ".1 M"
        // If UOM = Inches and value multiple of 12, display as feet
        // etc, etc
    }
}

不讨论是否应该通过调用代码将距离转换为正确的单位,这里的目标是使ToString方法能够在表示当前度量单位(英制或公制)的相同度量上向上或向下转换值。

显然,所有这些都可以硬编码到ToString方法中,但考虑到我还要为整个过程实现TypeConverters和FormatProviders,我希望找到一种通用的方法,从DistanceUnit中找出适当的下一个或上一个度量单位。

如果我想以这种方式实施,那么我是在做无用功吗?


1
我会像你建议的那样使用 [Flags] 来实现在 MSB 位中表示类别,低阶表示指数。 - kenny
3个回答

10

你真的需要一个枚举吗?也许,一个简单的值对象就足够了吗?

public class Distance
{
    private readonly decimal millimeters;

    public decimal Meters
    { 
        get { return millimeters * 0.001m; } 
    }

    private Distance(decimal millimeters)
    {
        this.millimeters = millimeters;
    }

    public static Distance Yards(decimal yards)
    {
        return new Distance(yards * 914.4m);
    }
}

使用扩展方法和适当定义的操作符,您可以获得非常类似Ruby语法的代码:

var theWholeNineYards = 9.Yards() + 34.Inches();

好答案。我已经为每个计量单位都拥有一个值类型,以及一个可以代表任何计量单位的Distance结构体。我的问题在于,当创建字符串表示形式(即Distance.ToString("G"))时,我希望该字符串使用同一系统(公制或英制)内最适合的UOM...因此出现了关于层次结构和枚举的问题。顺便说一下-在.NET的值类型中添加扩展方法不是有点禁忌吗? - Mark
您可以将用于创建“Distance”实例的UOM类型与“毫米”一起存储,然后使用它将其转换为字符串。或者,您可以拥有“MetricDistance”,“ImperialDistance”等子类。至于不允许的内容,我指的是值_对象_(这是一种设计模式),而不是值_类型_(这是.NET概念)。 - Anton Gogolev

2

一般来说,我会选择Anton的解决方案。但如果您的实现无法使用它,并且您需要类似于枚举的东西,我认为这是一种自然的方式来使用单位:

DistanceUnit.Metric.Millimeter  
DistanceUnit.Imperial.Inch  

为了像这样使用它,需要有:
public static class DistanceUnit  
{
  public static MetricDistanceUnit Metric;
  public static ImperialDistanceUnit Imperial;
}   

其中MetricDistanceUnit表示:

public enum MetricDistanceUnit  
{
   Millimeter, Centimeter ...
}

ImperialDistanceUnit 在结构上与之相同。


1
我喜欢你的建议,但我需要一个单一的值类型来保存所有枚举值(即不能将枚举拆分为MetricDistancEUnit和ImperialDistanceUnit)。 - Mark

1
也许你需要的只是一个返回对应单位子集的函数。
class UnitSystem
{
  public enum Type
  {
    Metric,
    Imperial
  }

  public static DistanceUnit[] GetUnits(Type type)
  {
    switch (type)
    {
      case Type.Metric:
        return new DistanceUnit[] {
          DistanceUnit.Millimeter,
          DistanceUnit.Centimeter,
          DistanceUnit.Meter,
          DistanceUnit.Kilometer
        }

      case Type.Imperial:
        return new DistanceUnit[] {
          DistanceUnit.Inch,
          DistanceUnit.Foot,
          DistanceUnit.Yard,
          DistanceUnit.Mile
        }
    }
  }

  public static Type GetType(DistanceUnit unit)
  {
    switch (unit)
    {
      case DistanceUnit.Millimeter:
      case DistanceUnit.Centimeter:
      case DistanceUnit.Meter:
      case DistanceUnit.Kilometer:
        return Type.Metric;

      case DistanceUnit.Inch:
      case DistanceUnit.Foot:
      case DistanceUnit.Yard:
      case DistanceUnit.Mile:
        return Type.Imperial;
    }
  }
}

将DistanceUnit转换回系统(英制或公制),使用SortedList或SortedSet是否过度?您如何有效地实现反向转换? - Mark
我不逃避switch语句,所以答案已相应地进行了编辑。 - Dialecticus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接