通用枚举详细信息列表类






3.48/5 (8投票s)
一篇描述简单通用类以提供常量或枚举值的用户友好文本的文章。
引言
我第一次接触通用编程是在 Visual Studio 2005 的 Beta 1 版本中。在此期间,我学到了更多关于它们是什么的知识。我了解到通用类是一个模板。也就是说,一个类型参数 <T>
被声明,当创建该类的对象时,编译器会将该声明的实例替换为一个实际的类。这带来了几个直接的好处:首先,类型安全得到了很大改进;其次,性能得到提高,因为 CLR 没有装箱和拆箱对象的开销。后来,在学习了框架本身提供的通用类(它们超出了 System.Collections.Generic
命名空间)之后,我开始思考,这很好,但我会有什么实际用途来创建我自己的通用类呢?
我错了,我本应该记住,一旦你学会了一项技术,迟早你会想到一种使用它的方法。
我最近在编写一个类来提供枚举值的描述性列表——这是一个我经常遇到的问题(见下文)。在我编写类的过程中,我突然有了灵感,意识到通过使用通用编程,我可以轻松创建一个优雅且可重用的类来解决这个常见问题。
本文和附带的演示介绍了这个类,演示了如何使用它,也许还能让像我一样找不到通用编程任何用途的人得到进一步的启示。
问题所在
在任何应用程序中,一个常见的问题是在下拉列表中呈现用户友好的选项列表,而在处理或存储方便起见,应用程序代码使用枚举或其他常量集来指示允许的值,并使用枚举或整数来存储它。
自从我开始使用 C# 以来,我一直通过创建一个 enum
来包含常量来解决这个问题,如果需要将它们呈现给用户进行选择,则需要一个描述类,该类至少有两个属性用于文本表示和 enum
值本身。在使用数据绑定时,我还发现提供一个进一步的属性很有用,该属性提供将 enum
转换为 int
、short
或 byte
(取决于绑定字段的类型),但稍后将详细介绍。为了创建列表,我通常会构建一个包含此类对象的静态数组,然后将其分配给表示层中下拉列表的数据源。
解决方案
示例代码创建了一个小型 Windows 应用程序,演示了如何创建列表、将其绑定到下拉列表,并显示所选列表项的 enum
和整数值。组合框绑定到类 ExampleDetails
的数组,该数组通过该类的静态属性访问,DisplayMember
属性绑定到 Text
属性,ValueMember
属性绑定到详细信息属性。当用户选择列表项时,将显示 SelectedValue
属性的值(enum
)以及通过访问基础对象(使用 SelectedItem
属性)得到的整数等效值。
此代码在 SelectedItemChanged
事件中实现,以下代码显示了该事件中的重要部分。通过使用 string.Format
方法设置了几个标签的 Text
属性,第一个标签仅显示 SelectedValue
属性的值,因为 ValueMember
已设置为该值,它将是 enum
。在该行下方,我使用 as
运算符将 SelectedItem
强制转换为 ExampleDetails
对象。值得指出的是,as
运算符非常有用,它将一个对象强制转换为指定的类,但如果无法转换,它会将变量设置为 null
而不是抛出 InvalidCastException
,然后您可以测试变量是否为 null
。我倾向于比常规强制转换更频繁地使用它。在强制转换之后,第二个标签会填充一个字符串,显示 enum
的整数值。
// display the enum value
label2.Text = string.Format( "Enum value: {0}",
cbExample.SelectedValue );
// get the details object
ExampleDetails details =
cbExample.SelectedItem as ExampleDetails;
// and display the integer value
label3.Text = string.Format( "Integer value: {0:n0}",
details.value );
这是 ExampleDetails
类
public class ExampleDetails : EnumDetails<ExampleEnum> {
public ExampleDetails( string text, ExampleEnum detail )
: base( text, detail ) {
}
/// <summary>
/// An array describing each of the enum values
/// </summary>
public static ExampleDetails[] details {
get {
return new ExampleDetails[] {
new ExampleDetails("First", ExampleEnum.exOne),
new ExampleDetails("Second", ExampleEnum.exTwo),
new ExampleDetails("Third", ExampleEnum.exThree)
};
}
}
}
ExampleDetails
继承自通用类 EnumDetails
。ExampleEnum
enum
在 EnumDetails
声明旁边用尖括号声明为类型参数,以表明这是通用模板应该“交换”进去的类。
为了将正确类型的数据传递给基类,创建了一个构造函数,该函数接受枚举的文本表示(例如,“First”)和 ExampleEnum
类型的值,通过调用基构造函数将这些参数传递给基类,多亏了通用声明,基类现在将期望一个字符串和一个 ExampleEnum
。静态属性提供了一个 ExampleDetails
数组,其中包含枚举值的描述性文本和值本身。
我应该指出,您不必继承 EnumDetails
,您可以创建一个类来创建并提供 EnumDetails<ExampleEnum>
的数组,这也会奏效,而且并没有什么问题。但我更喜欢继承,主要是因为我认为它更面向对象。通过创建 ExampleDetails
类,我拥有了一个类,其唯一职责是为 ExampleEnum
提供自身的详细信息列表。
这是 EnumDetails
类,嗯,至少是一部分
public class EnumDetails<T> where T : struct {
private string fText;
private T fEnum;
public EnumDetails( string text, T detail ) {
fText = text;
fEnum = detail;
}
/// <summary>
/// Description of the enum value
/// </summary>
public string text {
get {
return fText;
}
}
/// <summary>
/// The value itself
/// </summary>
public T detail {
get {
return fEnum;
}
}
EnumDetails
是通用基类,这里需要注意的关键是类声明中的 <T>
类型参数。它的作用是通知编译器该类是通用的,并且在代码中出现 T
的地方,都应该被替换为在类实现中指定的类型。因此,对于 ExampleDetails
实现,ExampleEnum
被声明为类型 T
,所以 EnumDetails
中任何声明 T
的地方都会被“交换”为 ExampleEnum
。因此,构造函数只会接受一个 ExampleEnum
值作为详细信息参数,然后将其存储在 fEnum
成员变量中。
通用编程的另一个有用功能是能够将类型 T
约束为特定类型或其派生类之一,这就是 'where T : struct
' 声明的作用。不幸的是,由于我的目标是提供枚举的文本表示,我确实希望能够约束 enum
,但您不能,最接近的是 struct
,这可以说更好,因为它也包括了数值类型。
/// <summary>
/// The enum value converted to an integer
/// </summary>
public int value {
get {
return Convert.ToInt32(fEnum);
}
}
/// <summary>
/// The enum value converted to a short
/// </summary>
public short shortValue {
get {
return Convert.ToInt16(fEnum);
}
}
/// <summary>
/// The enum value converted to a byte
/// </summary>
public byte byteValue {
get {
return Convert.ToByte( fEnum );
}
}
EnumDetails
还声明了三个其他属性:value
、shortValue
和 byteValue
,它们具有有趣的目的。我做了很多数据库编程,因此我代码中声明的 enum
可能在数据库(如果是 SQL Server)中是一个 int
、smallint
或 tinyint
列。如果我创建了一个类型化数据集,这些列将在数据集中声明为 int
、short
或 byte
。在下拉列表中绑定到其中一列时,将 ComboBox
的 ValueMember
设置为枚举属性是没有用的,绑定将永远不会起作用。这是因为类型不兼容,我的意思是,给定的枚举值(可能在内存中等于一个)永远不会等于表示为 int
的相同值,除非其中一个被强制转换为另一种类型(即 intValue == (int)enumValue
)。但是,如果我将 ComboBox ValueMember
设置为 value
属性,因为它将 enum
转换为 int
,绑定将正确匹配值并在 ComboBox
中显示正确的选择。
结论
EnumDetails
类是通用编程的一个简单实现,但我相信如果我将其添加到代码库并在我的项目中引用它,它最终将为我节省大量时间。这是因为使用 EnumDetails
后,实现此类列表将是一项非常琐碎的任务,只需几分钟即可完成。另外,EnumDetails
的每个实现都有助于对其进行测试,因此随着每个连续的实现,我可以更自信地认为实现将第一次就能正常工作。而这正是代码重用的意义所在。