之前介绍了C#枚举的基础与赋值相关的知识,本文继续介绍有关C#枚举的一些问答。
Q:我定义了一个这样的枚举:
- // Code #20
- public enum FontStyle
- {
- Bold,
- Italic,
- Regular,
- Strikethrough,
- Underline
- }
我用它来指定字体的风格,但我遇到了麻烦。你知道,字体可以同时拥有枚举里面所列举的一种或者多种风格,那么,我如何为字体同时指定多种风格呢?
A:这个时候你就需要位枚举(Bit Flags),把Code #20修改一下:
- // Code #21
- // I am using the FlagsAttribute to identify a bit flags.
- [Flags]
- public enum FontStyle
- {
- Bold = 0x0001,
- Italic = 0x0002,
- Regular = 0x0004,
- Strikethrough = 0x0010,
- Underline = 0x0020
- }
现在,你可以通过按位或运算来为字体指定多种风格了:
- // Code #22
- // See Code #21 for FontStyle.
- Font f = new Font(
- FontFamily.GenericSansSerif,
- 12.0F,
- FontStyle.Italic | FontStyle.Underline
- );
--------------------------------------------------------------------------------
Q:位枚举同样存在类似于Code #15的恶作剧吧?
A:是的,例如:
- // Code #23
- // See Code #21 for FontStyle.
- class Program
- {
- static void Main()
- {
- Bar(FontStyle.Regular | (FontStyle)0x0400);
- }
- static void Bar(FontStyle fs)
- {
- // Code here
- }
- }
--------------------------------------------------------------------------------
Q:那么,System.Enum.IsDefine方法是否还能应对呢?
A:不能。位枚举成员并不具备排他性,多个成员可以通过按位或运算组合起来。而System.Enum.IsDefine方法只能判断枚举变量的值是否为某一已定义的枚举成员。请看如下代码:
- // Code #24
- // See Code #21 for FontStyle.
- FontStyle fs1 = FontStyle.Bold | FontStyle.Italic | FontStyle.Underline;
- Console.WriteLine(Enum.IsDefine(typeof(FontStyle), fs1));
- FontStyle fs2 = FontStyle.Regular | (FontStyle)0x0400;
- Console.WriteLine(Enum.IsDefine(typeof(FontStyle), fs2));
- // Output:
- // false
- // false
我们对代码的输出毫无疑问,因为fs1和fs2都不是一个单独的枚举成员。但这不是我们所追求的答案,我们希望区别对待fs1和fs2,至少我们不希望fs2中的捣蛋家伙——(FontStyle)0x0400——在我们的程序中搞破坏!
--------------------------------------------------------------------------------
Q:那么,我们是否有办法隔离这些捣蛋鬼呢?
A:Of course!我们同样可以使用条件判断语句来处理,但做法将与Code #17和Code #18有所不同。现在我们把Code #23改进如下:
- // Code #25
- // See Code #21 for FontStyle.
- class Program
- {
- static void Main()
- {
- Bar(FontStyle.Regular | (FontStyle)0x0400);
- }
- static void Bar(FontStyle fs)
- {
- if ((fs & FontStyle.Bold) != 0)
- {
- // Do something associated with bold
- }
- if ((fs & FontStyle.Italic) != 0)
- {
- // Do something associated with italic
- }
- // Other conditional code continues here
- }
- }
我们把枚举变量与某一特定的位枚举成员进行按位与运算,若结果不为0则表明枚举变量中包含着该位枚举成员。当然,你也可以为自己的代码写一组这样的方法:
- // Code #26
- // See Code #21 for FontStyle.
- static bool ContainsBold(FontStyle fs)
- {
- if ((fs & FontStyle.Bold) != 0)
- return true;
- else
- return false;
- }
- static bool ContainsItalic(FontStyle fs)
- {
- if ((fs & FontStyle.Italic) != 0)
- return true;
- else
- return false;
- }
- // Other similar methods continue here
又或者你可以写一个这样的方法:
- // Code #27
- // See Code #21 for FontStyle.
- static bool ContainsMember(FontStyle fs, FontStyle menber)
- {
- if ((fs & member) != 0)
- return true;
- else
- return false;
- }
如果你只希望判断某一个枚举变量里面是否包含捣蛋鬼,你可以写一个这样的方法:
- // Code #28
- // See Code #21 for FontStyle.
- static bool ContainsPranksters(FontStyle fs)
- {
- if ((fs < FontStyle.Bold) || (fs > (FontStyle)0x0037))
- return true;
- if (fs == (FontStyle)0x0008 ||
- fs == (FontStyle)0x0009 ||
- fs == (FontStyle)0x0018 ||
- fs == (FontStyle)0x0019 ||
- fs == (FontStyle)0x0028 ||
- fs == (FontStyle)0x0029)
- return true;
- return false;
- }
留个“作业”吧,知道为何这样可以判断出是否有捣蛋鬼吗?当然,如果你想到了更好的方法,记住要告诉我哟!
--------------------------------------------------------------------------------
Q:慢着!你那个“我们把枚举变量与某一特定的位枚举成员进行按位与运算,若结果不为0则表明枚举变量中包含着该位枚举成员”,在以下的情况显然不成立的:
- // Code #35
- [Flags]
- enum Music
- {
- Jazz = 0x00,
- Rock = 0x01,
- Country = 0x02,
- Classic = 0x03
- }
- static void Main()
- {
- Music m = Music.Rock | Music.Jazz;
- int r = (int)(m & Music.Classic);
- Console.WriteLine(r);
- }
该代码的输出恰恰就为0,然而m却不包含Music.Classic!
A:Good question!也正如你所看到的,这种做法其实与位枚举成员的值是如何被赋予息息相关的。那么,我们应该如何为位枚举的成员赋值呢?由于位枚举成员的值和位运算都直接与二进制相关,所以,我们不妨从二进制的角度去探索一下如何恰当的为位枚举的成员赋值。
试想一下,如果我们能把二进制值的字面特征与位枚举成员关联起来,使得我们能够直接从二进制值的字面特征判断位枚举成员的存在与否该多好呀!
考察你的Music枚举,它有4个成员,那么我们把二进制值的数位设定为4,即[D][C][B][A]型;并规定每一个数位代表该一个枚举成员,即A代表Music.Jazz、B代表Music.Rock、C代表Music.Country、D代表Music.Classic;那么,某个数位的值为1就代表其对应的枚举成员存在,为0则不存在。现在,假如Music的某个变量m的二进制值为0110,我们就可以肯定的说,m中包含着Music.Rock和Music.Country,因其B、C数位的值均为1。
那么这些跟为位枚举成员赋值有什么关系呢?从上面的讨论可以知道,Music各个成员的二进制值分别为:Music.Jazz为0001、Music.Rock为0010、Music.Country为0100、Music.Classic为1000。把这些值转换为十六进制值并赋予对应的成员:
- // Code #36
- [Flags]
- enum Music
- {
- Jazz = 0x01,
- Rock = 0x02,
- Country = 0x04,
- Classic = 0x08
- }
这样,你就可以采用我所提到的方法来验证某个枚举变量中是否包含着特定的枚举成员了。
--------------------------------------------------------------------------------
Q:如何把C#枚举类型转换(解析)成字符串类型?
A:最简单的方法就是使用System.Enum的
- public override string ToString();
方法,或者把枚举类型转换为IConvertible接口,再调用该接口的
- string ToString(IFormatProvider provider);
方法。此时你将得到枚举成员的字面值的字符串:
- // Code #29
- // See Code #01 for Alignment.
- // See Code #21 for FontStyle.
- static void Main()
- {
- Alignment a = Alignment.Right;
- Console.WriteLine("Alignment is {0}.", a.ToString());
- FontStyle fs = FontStyle.Bold | FontStyle.Underline;
- Console.WriteLine("FontStyle is {0}.", fs.ToString());
- }
- // Output:
- // Alignment is Right.
- // FontStyle is Bold, Underline.
如果你希望输出枚举成员的值,那么你可以手动指定格式参数:
- // Code #30
- // See Code #01 for Alignment.
- // See Code #21 for FontStyle.
- static void Main()
- {
- Alignment a = Alignment.Right;
- // Represents Alignment in decimal form.
- Console.WriteLine("Alignment is {0}.", a.ToString("d"));
- // Represents Alignment in hexadecimal without a leading "0x".
- Console.WriteLine("Alignment is {0}.", a.ToString("x"));
- FontStyle fs = FontStyle.Bold | FontStyle.Underline;
- // Represents FontStyle in decimal form.
- Console.WriteLine("FontStyle is {0}.", fs.ToString("d"));
- // Represents FontStyle in hexadecimal without a leading "0x".
- Console.WriteLine("FontStyle is {0}.", fs.ToString("x"));
- }
- // Output:
- // Alignment is 2.
- // Alignment is 00000002.
- // FontStyle is 33.
- // FontStyle is 00000021.
除此之外,你还可以使用System.Enum的
- public static string Format(
- Type enumType,
- object value,
- string format
- );
方法:
- // Code #31
- // See Code #01 for Alignment.
- static void Main()
- {
- Alignment a = Alignment.Right;
- Console.WriteLine(
- "Alignment is 0x{0}.",
- System.Enum.Format(typeof(Alignment), a, "x")
- );
- Console.WriteLine("Alignment is 0x{0:x}.", a);
- }
- // Output:
- // Alignment is 0x00000002.
- // Alignment is 0x00000002.
另外,你还可以通过System.Enum的
- public static string[] GetNames(Type enumType);
来获取枚举所有成员的字面值:
- // Code #32
- // See Code #01 for Alignment.
- static void Main()
- {
- string[] names = Enum.GetNames(typeof(Alignment));
- foreach(string name in names)
- Console.WriteLine(name);
- }
- // Output:
- // Left
- // Center
- // Right
--------------------------------------------------------------------------------
Q:如果我得到一个表示枚举成员的字符串,我如何将其解析为对应枚举类型呢?
A:这时你就需要System.Enum的
- public static object Parse(
- Type enumType,
- string value,
- bool ignoreCase
- );
方法了:
- // Code #33
- // See Code #01 for Alignment.
- // See Code #21 for FontStyle.
- static void Main()
- {
- string name = "Right";
- Alignment a = (Alignment)Enum.Parse(typeof(Alignment), name, false);
- Console.WriteLine(a.ToString());
- string names = "Bold, Italic, Underline";
- FontStyle fs = (FontStyle)Enum.Parse(typeof(FontStyle), names, false);
- Console.WriteLine(fs.ToString());
- }
- // Output:
- // Right
- // Bold, Italic, Underline
--------------------------------------------------------------------------------
Q:枚举类型为我们编码提供了巨大的便利,什么情况下我们不应该使用枚举呢?
A:首先你应该清楚枚举类型在编程中充当一个什么样的角色。在我看来,枚举类型表达了一种稳定的分类标准。当你查看.NET Framework BCL中的枚举类型,你会发现它们几乎没有任何改变的可能或者趋势,表现出一种稳定性。所以,当你所要表达的分类标准也同样具备这种稳定性时,你就可以考虑枚举类型了。那么什么情况下不使用枚举呢?一般说来,当分类标准不闭合时——即新的子分类随时有可能产生或者现有子分类随时有可能被替换——你就应该考虑使用其他的方式来表达了。
下面让我们来看一个薪酬自动管理系统的一部分,假设某公司现有雇员种类为:
1. Programmer
2. Salesman
3. Manager
相关代码如下:
- // Code #34
- public enum EmployeeKind
- {
- Programmer,
- Salesman,
- Manager
- }
- public class Employee
- {
- public Employee(string name, EmployeeKind kind)
- {
- Kind = kind;
- }
- private string m_Name;
- public string Name
- {
- get { return m_Name; }
- }
- public readonly EmployeeKind Kind;
- public double GetPayment()
- {
- switch(Kind)
- {
- case EmployeeKind.Programmer:
- // Return payment
- case EmployeeKind.Salesman:
- // Return payment
- case EmployeeKind.Manager:
- // Return payment
- }
- }
- }
假如该公司正处于成长期,那么公司的组织结构发生改变是家常便饭之事。但公司每一次改变组织结构,这样的系统就要经历源代码修改、重新编译然后再部署的过程!而且,如果公司实行了新的绩效评估方案,并把薪酬计算与绩效追踪挂钩,那么GetPayment方法将进一步壮大,以至于最终其代码晦涩难懂。你当然可以把GetPayment分割为子方法,但并没有什么实质的改变。再想一想,如果将来公司打算把薪酬系统与银行挂钩,提供薪资直接银行划拨,那将又会是怎么一番局面呢?
以上就总结了一些C#枚举应用情景的相关问答。
【编辑推荐】