使用 FlagsAttribute 处理枚举的便捷方法






1.83/5 (4投票s)
枚举的位运算。
引言
我被分配了创建这样一个应用程序的任务:该应用程序需要解析几个 RSS 源,以便汇总来自不同来源和不同类别的最新新闻标题,包括头条新闻、体育、商业、娱乐、政治等。
最终的顶层类是 ContentMonitor
,它会在每个指定的时间间隔下载新内容。当创建一个 ContentMonitor
对象实例时,我希望类的使用者能够指定它应该监控哪些源,以及从这些源编译并导出到本地 XML 文档的主标题列表。
仔细考虑后,我决定创建一个实现 FlagsAttribute
的 NewsCategory
枚举,这样类的使用者就可以将任何新闻类别的组合传递给 ContentMonitor
,然后 ContentMonitor
负责检索这些指定的标题。
监视器类的实现与本文无关,但我希望展示如何使用位运算将一个“列表”的枚举值传递给一个对象,然后在另一端检索这些值以确定正在请求哪些标题。
背景
如果您需要熟悉位运算,请查看 这篇文章。关于 FlagsAttibute
类的官方 MSDN 文档可以在 这里 找到。我不会详细介绍位运算的工作原理。我假设如果您正在阅读本文,您已经对它有所了解。
使用代码
闲话少说,直接上代码。
这是我的 NewsCategory
枚举的声明
[FlagsAttribute]
public enum NewsCategory : int
{
TopHeadlines =1,
Sports=2,
Business=4,
Financial=8,
World=16,
Entertainment=32,
Technical=64,
Politics=128,
Health=256,
National=512
}
这是 ContentMonitor
中设置 NewsCategory
成员的属性
public NewsCategory ContentCategories
{
set
{
int[] arr = (int[])System.Enum.GetValues(typeof(NewsCategory));
int largest = GetLargestValue(arr); //value of largest enum constant
int smallest = GetSmallestValue(arr); //value of smallest enum constant
for (int i = smallest; i <= largest; i = i * 2) // i * 2 because of bitwise flags
{
switch ((NewsCategory)(value & (NewsCategory)i))
{
case NewsCategory.Business: break;
case NewsCategory.Entertainment: break;
case NewsCategory.Financial: break;
case NewsCategory.Health: break;
case NewsCategory.National: break;
case NewsCategory.Politics: break;
case NewsCategory.Sports: break;
case NewsCategory.Technical: break;
case NewsCategory.TopHeadlines: break;
case NewsCategory.World: break;
default: break;
}
}
}
}
最后,这是 ContentMonitor
如何通过按位或运算“告知”它要消耗哪些新闻内容。
ContentMonitor mon = new ContentMonitor();
// Tell the monitor which news headlines we want
mon.ContentCategories = NewsCategory.Business |
NewsCategory.Entertainment |
NewsCategory.Politics;
所以,我们来分解一下。
我首先想要做的是检索 NewsCategory
枚举的最小和最大常量值。假设 GetLargestValue()
和 GetSmallestValue()
方法分别返回传递给它们的数组中的最大和最小数字。
现在,我设置了一个 for
循环,从最小的枚举值开始,以二的倍数递增到最大的值。如果最小的枚举常量是 1,最大的常量是 512,那么在循环的每次迭代中,i
将会是 1、2、4、8、16、32、64 等,一直到 512。请注意,通过动态获取最小和最大的常量,我没有在 for
循环中有任何硬编码的数字。如果我决定在枚举中添加一个新项并为其分配值 1024 (512 * 2),我只需要更新枚举声明本身。这样,循环就仍然知道需要多少次迭代才能涵盖所有枚举值。
现在我们已经将一个“列表”的按位 NewCategory
值传递给了 ContentMonitor
,我们如何知道我们处理了哪些值?这就是按位与运算符发挥作用的地方。一种检索值的方法如下:
if ((value & NewsCategory.Politics) == NewsCategory.Politics) {...}
if ((value & NewsCategory.Business) == NewsCategory.Business) {...}
if ((value & NewsCategory.Financial) == NewsCategory.Financial) {...}
主要出于可读性的考虑,我不希望在一系列 if
语句中进行大量的按位与运算,因为我需要在同一行上重复输入 NewsCategory
。我总是喜欢能够编写简洁易读的代码模块。(是的,我可以将按位与运算放在一个函数中,然后以这种方式传递新闻类别,但我也 dislike 这种做法。)
如果您查看 switch
语句,它的功能与上面的一系列 if
语句基本相同。它获取 i
的当前值(代表一个枚举常量值),并将其类型转换为 NewsCategory
枚举。这很简单。之后,它使用传递给属性的 NewsCategory
值,并对其执行按位与运算,结果是一个可以用于 switch
语句的 NewsCategory
值。这基本上就像这样做:
value = NewsCategory.Business | NewsCategory.Entertainment;
NewsCategory tmpCat = (NewsCategory)4; // Returns NewsCategory.Business
NewsCategory newCat = (value & tmpCat);
if (newCat == NewsCategory.Business)
{
// We've got business
}
现在,只需在 switch
的主体中测试您的条件。我可能花在这方面的创造和书写上的时间比实际需要的要多,但这帮助我更深入地理解了如何使用位运算将多个值传递给类,并提取它们以对每个值执行特定操作。希望这在您未来的工作中能有所帮助。此外,如果您觉得本文中有任何错误或不准确之处,请不要犹豫告诉我。我喜欢反馈和学习新东西。