使用隐式枚举字段来表示数值是一种不好的做法吗?

Posted

技术标签:

【中文标题】使用隐式枚举字段来表示数值是一种不好的做法吗?【英文标题】:Is the use of implicit enum fields to represent numeric values a bad practice? 【发布时间】:2012-09-26 00:41:13 【问题描述】:

使用隐式枚举字段来表示数值一定是不好的做法吗?

这是一个用例:我想要一种简单的方法来表示十六进制数字,并且由于 C# 枚举是基于整数的,因此它们看起来很自然。我不喜欢这里的charstring,因为我必须明确地验证它们的值。枚举的问题是数字 [0-9] 不是有效的字段标识符(有充分的理由)。我突然想到我不需要声明数字0-9,因为它们是隐式存在的。

所以,我的十六进制数字枚举看起来像:

public enum Hex : int  
    A = 10,
    B = 11,
    C = 12,
    D = 13,
    E = 14,
    F = 15

所以,我可以写Tuple<Hex,Hex> r = Tuple.Create(Hex.F,(Hex)1);,而r.Item1.ToString() + r.Item2.ToString() 会给我“F1”。基本上,我的问题是,如果我想将数字常量的 ToString() 值命名为枚举字段,为什么完全省略声明会有问题?

作为枚举的替代表示可以使用一些前缀声明的字段,例如:

public enum Hex : int 
    _0 = 0,
    _1 = 1,
    _2 = 2,
    _3 = 3,
    _4 = 4,
    _5 = 5,
    _6 = 6,
    _7 = 7,
    _8 = 8,
    _9 = 9, 
    A = 10,
    B = 11,
    C = 12,
    D = 13,
    E = 14,
    F = 15

问题是上面的例子会给我“F_1”而不是“F1”。显然,这很容易解决。我想知道我没有考虑的隐式方法是否还有其他问题。

【问题讨论】:

你的意思是你必须从字符串转换为整数或类似的东西吗? 否则,为什么不只是字节值 = 0xA;? @Daryn,因为我想要一个数字,而不是一个数字。你可以很容易地写出 0xAAA,这是我不想允许的。 【参考方案1】:

我将为HexDigit 定义一个struct。您可以将 HexDigit 'A' 添加到 'F' 作为静态常量(或静态只读字段)。

您可以定义隐式转换器以允许转换整数 0-9、转换为整数,并且您可以重写 ToString() 以使元组看起来不错。

这将比枚举灵活得多。

【讨论】:

这是个好建议。我会对为什么枚举是一个糟糕的选择更感兴趣 当枚举功能足以解决您的问题时,枚举是很好的。在这种情况下,他们没有。当然,您可以使用枚举来近似解决方案,但是为什么要麻烦呢?定义正确的类型将使代码更易于阅读和扩展/维护。【参考方案2】:

这是处理十六进制的根本方法。 Hex 是一个人机界面细节。它总是一个字符串,一个数字的表示。就像“1234”是值 1234 的表示。当它以十六进制表示时恰好是“4D2”,但程序中的数字仍然是 1234。程序应该只关心数字,而不是表示。

仅当您将数字显示到人眼时才应该将数字转换为十六进制。使用 ToString("X") 很简单。并使用 NumberStyles.HexNumber 使用 TryParse() 从人工输入中解析回来。输入和输出,在其他任何时候你都不应该处理十六进制。

【讨论】:

如果我为 CSS 文档定义 RGB 颜色怎么办?看起来十六进制在那里是合适的。 同样,您正在为 CSS 文档生成 字符串 你能帮我理解为什么使用 C# 的类型安全来定义一个被解释为十六进制数字字符串的字符串是错误的,而我们通常对十进制数字字符串做同样的事情吗? (我正在考虑的一个示例是 ASP.NET API,它接受整数参数,但会导致向 UI 输出字符串)【参考方案3】:

这是一种不好的做法,因为它是一个聪明的技巧,会让阅读您的代码的人感到惊讶。令我惊讶的是它确实有效,它让我说 wtf.记住唯一有效的代码质量衡量标准:

聪明的技巧不属于应该由他人阅读和维护的代码。如果要将数字输出为十六进制,请使用普通的String.Format("0:X", value)将其转换为十六进制字符串

【讨论】:

+1,这是迄今为止最强的推理,imo。但是,我将十六进制结构/枚举中的值视为受约束的文字输入,而不仅仅是简单的输出格式。在这种情况下,0:X 不适用 使用 0xF1 进行受约束的文字输入有什么问题?如果需要,可以对数字进行范围检查,并在需要将其输出为十六进制时将其转换为字符串?我不确定我是否完全理解您的预期用例。 +1 用于引用 Robert C. Martin 的“Clean Code: A Handbook of Agile Software Craftsmanship”的介绍页面。 amazon.ca/Clean-Code-Handbook-Software-Craftsmanship/dp/…【参考方案4】:

我不确定您在这里实际想要完成什么,但如果您希望将某些内容限制为两个十六进制数字,为什么不将其声明为一个字节?虽然您的 enum hack 很聪明,但我实际上并不认为需要它。如果在没有解释的情况下将其传递给其他程序员,也可能会被误解,因为您对枚举使用未声明的值是违反直觉的。

关于数字基数和文字表示,计算中的整数本身不是以 10 为基数或以 16 为基数,实际上它实际上是以 2 为基数(二进制),并且任何其他表示都是人类的方便。该语言已经包含以十进制和十六进制格式表示文字数字的方法。 Limiting the number is a function of appropriately choosing the type.

如果您试图将某些内容限制为任意偶数的十六进制数字,那么像这样简单地初始化一个字节数组可能更合适:

    byte[] hexBytes = new byte[3]  0xA1, 0xB2, 0xC3 ;

此外,通过将您的值保持为常规数值类型或使用字节数组而不是将其放入带有枚举的元组中,您可以轻松访问整个范围的操作,否则会变得更加困难。

关于将数字限制为任意奇数的十六进制数字,您可以选择至少包含所需值 + 1 位的类型并在运行时限制该值。一种可能的实现如下:

    public class ThreeNibbleNumber
    
        private _value;
        public ushort Value
        
            get
            
                return _value;
            
            set
            
                if (value > 4095)
                
                    throw new ArgumentException("The number is too large.");
                
                else
                
                    _value = value;
                
            
        

        public override string ToString()
        
            return Value.ToString("x");
        
    

在您的另一个答案中,您引用了使用 CSS 颜色的想法。如果这就是您想要的,这样的解决方案似乎很合适:

    public struct CssColor
    
        public CssColor(uint colorValue)
        
            byte[] colorBytes = BitConverter.GetBytes(colorValue);

            if (BitConverter.IsLittleEndian)
            
                if (colorBytes[3] > 0)
                
                    throw new ArgumentException("The value is outside the range for a CSS Color.", "s");
                

                R = colorBytes[2];
                G = colorBytes[1];
                B = colorBytes[0];
            
            else
            
                if (colorBytes[0] > 0)
                
                    throw new ArgumentException("The value is outside the range for a CSS Color.", "s");
                

                R = colorBytes[1];
                G = colorBytes[2];
                B = colorBytes[3];
            
        

        public byte R;
        public byte G;
        public byte B;

        public override string ToString()
        
            return string.Format("#0:x1:x2:x", R, G, B).ToUpperInvariant();
        

        public static CssColor Parse(string s)
        
            if (s == null)
            
                throw new ArgumentNullException("s");
            

            s = s.Trim();
            if (!s.StartsWith("#") || s.Length > 7)
            
                throw new FormatException("The input is not a valid CSS color string.");
            

            s = s.Substring(1, s.Length - 1);

            uint color = uint.Parse(s, System.Globalization.NumberStyles.HexNumber);

            return new CssColor(color);
        
    

【讨论】:

【参考方案5】:

我并不特别明白您为什么要这样做,但是您可以在每个枚举值上使用 Description 属性来摆脱 _ 并创建某种静态函数,使您可以获取其中一个你的枚举值很容易像 Hex(15) -> 'F'。

public enum Hex 
    [Description("0")] _0 = 0,
    ...

【讨论】:

【参考方案6】:

在我看来,这是一种不好的做法。如果您需要 Hex 表示,只需创建一个帮助类来处理您需要的所有操作。

正如this article 建议的那样,这些代码 sn-ps 将有助于创建助手:

// Store integer 182
int decValue = 182;
// Convert integer 182 as a hex in a string variable
string hexValue = decValue.ToString("X");
// Convert the hex string back to the number
int decAgain = int.Parse(hexValue, System.Globalization.NumberStyles.HexNumber);

我认为这是不好的做法的原因是因为它不是面向对象的,并且遇到了依赖枚举来转换硬编码的所有值的问题——这很糟糕。如果您可以避免对任何内容进行硬编码,那么这始终是朝着正确方向迈出的一步。此外,帮助程序类是可扩展的,并且可以随着时间的推移进行改进以提供更多功能。

话虽如此,我确实喜欢枚举的简单性,但是,在我看来,这并不能取代保持 OO 的需要。

【讨论】:

以上是关于使用隐式枚举字段来表示数值是一种不好的做法吗?的主要内容,如果未能解决你的问题,请参考以下文章

用私有属性替换类中的每个字段是一种不好的做法吗? [复制]

JavaScript:动态扩展原型是一种不好的做法吗?

Flutter BLoC:使用嵌套的 StreamBuilders 是一种不好的做法吗?

将 AppDelegate 用作 Singleton 是一种不好的做法吗?

使用 js 渲染 html 是一种不好的做法吗? [关闭]

使用 Java 标准密钥库是一种不好的做法吗