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

发布于 2024-09-28 20:16:25 字数 1738 浏览 2 评论 0原文

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

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 ....

等,以及手工编码的不可变距离,旨在代表任何测量:

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

... 应通过调用代码将其转换为正确的 UOM,此处的目标是 ToString 在当前 UnitOfMeasure 表示的同一度量(英制或公制)上向上或向下转换值。

显然,这一切都可以硬编码到 ToString 方法中,但考虑到我还为整个 shibang 实现了 TypeConverters 和 FormatProviders,我想找到一种通用方法,从 DistanceUnit 中找出合适的下一步-向上或向下的测量单位是。

我想以这种方式实施是不是找错了方向?

I'm implementing value types which represents a distance (or length).
There's an enum that represents the different units of measure, eg:

public enum DistanceUnit 
{
   Millimeter,
   Centimeter,
   Meter,
   Kilometer,
   Inch,
   Foot,
   Yard,
   Mile
};

These measurements fall into one of two systems - either metric or imperial. Since enum doesn't support hierachies, what's the best pattern for representing this hierarchy?

Is it to use bit flags?
Or use two seperate enums along with some methods to correlate them?
Or to declare static members instead of enums?

Give me some advice... how would you implement this?


Edit - More clarification:
I have several immutable structs (generated by T4) which represent various measurements:

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 ....

... etc, as well as a hand-coded immutable Distance, intended to represent any measure:

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

Foregoing a discussion about whether the Distance should be converted to the correct UOM by calling code, the goal here is for ToString to convert the value up or down on the same measurement (imperial or metric) that is represented by the current UnitOfMeasure.

Obviously this could all be hard coded into the ToString method, but given that I'm also implementing TypeConverters and FormatProviders for this whole shibang, I'd like to find a generic means of figuring out, from a DistanceUnit, what the appropriate next-up or next-down unit of measure would be.

Am I barking up the wrong tree by wanting to implement in this manner?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

转角预定愛 2024-10-05 20:16:25

你真的需要一个enum吗?也许,一个简单的 值对象 就可以了?

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

Do you really need an enum here? Maybe, a simple value object will do?

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

With extension methods you and properly defined operators can get very Ruby-like syntax:

var theWholeNineYards = 9.Yards() + 34.Inches();
回忆那么伤 2024-10-05 20:16:25

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

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 具有相同的结构..

Generally speaking, I would go with Anton's solution. But if your implementation can't use that, and you need things to be used similar to an enum,
I think this is a natural way to use the units:

DistanceUnit.Metric.Millimeter  
DistanceUnit.Imperial.Inch  

In order to use it like that, there should be:

public static class DistanceUnit  
{
  public static MetricDistanceUnit Metric;
  public static ImperialDistanceUnit Imperial;
}   

Where MetricDistanceUnit is:

public enum MetricDistanceUnit  
{
   Millimeter, Centimeter ...
}

And ImperialDistanceUnit has the same structure..

不甘平庸 2024-10-05 20:16:25

也许您需要的只是一个返回相应单位子集的函数

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

Maybe all you need is a function that returns a corresponding unit subset

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;
    }
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文