65.9K
CodeProject 正在变化。 阅读更多。
Home

StringEnum 模拟 System.Enum 的工作方式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.06/5 (5投票s)

2007 年 4 月 29 日

CPOL

6分钟阅读

viewsIcon

56908

downloadIcon

745

一个基类,用于密切模拟具有字符串底层类型的枚举行为。

引言

如果您曾经维护、使用和比较过一串字符串常量,您可能已经遇到过这样的挫败感:即使在 .NET 2.0 中,我们也还没有原生的字符串枚举。此外,您无法继承 System.Enum 类,因此无法扩展它来支持字符串。

一个 StringEnum 在定义可能的数据库值、保存用于填充列表的字符串值的可枚举列表等方面将非常有用。已经创建了许多变通方法,但它们都有各自的问题。

只是一个警告:实现可能有点粗糙,因为我只是想尽快发布一些内容供人们查看和开始使用。话虽如此,欢迎反馈。

背景

我见过几种不同的方法来实现类似真正的字符串枚举的功能。它们如下:

  • Enum.ToString - 那么为什么不直接使用一个简单的 Enum,然后只使用 ToStringParse 函数来相互转换字符串呢?
    • 优点包括易于定义,以及大多数常规 Enum 的优点。
    • 主要缺点是名称严格绑定到其值。像 "PF_SCR_ATTRIB_WIDESCREEN" 这样的名称被不必要地装饰,而 "SA" 被不必要地缩写。它们可以更好地表达为 "Widescreen" 或 "Sales"。 (我知道 XML 注释可以缓解,但我们正在努力实现清晰、简洁的代码。) 此外,每次想处理字符串值时,都必须使用 ToStringParse 函数。此外,所有排序都按数字值进行,并且枚举类无法扩展。
  • Attributes (属性) - 在此方法中,定义了一些自定义属性并将其附加到常规的 Enum。 (一个很好的例子可以在 这里找到。)
    • 由于它是一个 Enum,因此优点是 Enum 的所有优点——可以获取值的列表,可以从字符串解析名称,等等。
    • 缺点是必须显式调用一个函数来“挖掘”属性以解析值或返回一个值,而且由于该函数是通用的,因此在手动输入函数名称、然后输入枚举名称和 "." 之前,您不会获得 IntelliSense 的任何好处。此外,基类型仍然是数字,因此赋值和比较仍然在数字级别上进行。
  • Constants (常量) - 使用一些鲜为人知的技术(在 StringEnum 代码中使用),在一个类上定义一组常量实际上是一个相当不错的方法。
    • 优点是,由于您的枚举值都是字符串常量,因此所有比较都是字符串比较。
    • 缺点是字符串比较只是普通的字符串比较,依赖于 Option Compare 或大量的 StrComp 来区分大小写。此外,没有类型检查,并且不允许无名称值。
  • Structure (结构) - 结构的一个例子可以在 这里找到。
    • 这是最好的方法之一,因为结构的行为像值类型,这使得概念上易于跟踪。可以重载运算符,并且您可以实现自己的函数来使用该结构。
    • 然而,对于结构,没有继承,并且所有扩展字符串枚举的代码都必须四处复制——如果您重载的不仅仅是 Equals 函数,则很困难。另一个问题是,结构实际上不是必需的,因为您最终所做的只是复制对 ReadOnly 字符串值的引用。不如直接传递一个类的引用。

Using the Code

StringEnumBase 提供了一个可继承的基类,它提供了与 System.Enum 类相当的功能。

它具有以下优点:

  • 类型检查。
  • 比较发生时 IntelliSense 弹出。
  • 自定义鼠标悬停,显示名称和值。
  • 使用基本运算符进行受控比较。默认不区分大小写。可以通过将提供的属性分配给继承类来区分大小写。
  • 可以接受名称值以外的值。可以通过将提供的属性分配给继承类来仅限于名称值。
  • 可以使用 System.ComponentModel.Description 为每个命名值分配描述,并通过基类的 .ToDesc 函数获取它。
  • 提供 System.Enum 类所有其他相关函数(例如 GetValuesParseCompareToGetNames 等)。

以以下方式继承它,将继承者的类型名称作为泛型类型。这样基类就可以获取继承类的类型——用于获取属性、字段等。您还必须声明参数化构造函数,以便它可以保持私有和参数化。

添加 completionlist XML 注释以获取 IntelliSense 弹出。 StringEnumRegisteredOnly 属性是用于修改 StringEnumBase 行为的可选属性之一。

''' <completionlist cref="Numbers" />
<StringEnumRegisteredOnly()> _
Public Class Numbers
    Inherits StringEnumBase(Of Numbers)
    Private Sub New(ByVal StrValue As String)
        MyBase.New(StrValue)
    End Sub  
 
    ...

然后,您可以将值枚举为 Shared ReadOnly 字段或属性。我更喜欢使用字段,因为它占一行。您也可以在声明中使用 DescriptionAttribute

<Description("This is test value one.")> _
Public Shared ReadOnly One As New Numbers("ONE")

<Description("This is test value two.")> _
Public Shared ReadOnly Property Three() As Numbers
    Get 
        Return New Numbers("TWO")
    End Get
End Property 

关注点

看起来 .NET 的一些部分仍然很粗糙,即使经过这么多年——尤其是在 VB.NET 中。例如,在 DebuggerDisplayAttribute 中,后缀 "nq" 被记录为会去除其后缀值中的引号。唯一的问题是它只适用于 C#。看起来内联条件也只适用于 C#。

我还偶然发现了 completionlist XML 注释。为什么这个功能如此方便却没有任何文档?

历史

  • 2007-05-02 - 初始发布。
  • 2007-05-22 - 修改以更好地符合预期行为并添加一些功能。
    • 添加了更多比较运算符函数,以处理 StringEnum 直接与字符串或其他可以转换为字符串的对象(包括不同类型的 StringEnum)进行比较的情况。这允许与字符串进行比较,而无需将字符串转换为 StringEnum 类型,如果 StringEnum 设置为仅接受已注册的值,这可能会导致错误。
    • 添加了向字符串的缩小转换。声明为缩小转换很重要,因为其他对象将在 StringEnum 转换为字符串之前转换为 StringEnum,这使我们能够在某些情况下控制大小写敏感性。
    • 实现了 IConvertible 接口,以便 StringEnum 将自动转换为字符串;例如,当它传递给一个期望字符串参数的函数时。这一点很重要,因为 CType 并非在所有情况下都会自动调用。
    • 修改了 GetValues() 函数以返回一个类型化数组而不是泛型 System.Array
    • 添加了用于解析描述以获取 StringEnum 值的函数。
    • 添加了共享属性,用于返回特定的 StringEnum 是否正在使用行为修改属性。
© . All rights reserved.