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

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

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.83/5 (4投票s)

2008年9月10日

CPOL

4分钟阅读

viewsIcon

26910

枚举的位运算。

引言

我被分配了创建这样一个应用程序的任务:该应用程序需要解析几个 RSS 源,以便汇总来自不同来源和不同类别的最新新闻标题,包括头条新闻、体育、商业、娱乐、政治等。

最终的顶层类是 ContentMonitor,它会在每个指定的时间间隔下载新内容。当创建一个 ContentMonitor 对象实例时,我希望类的使用者能够指定它应该监控哪些源,以及从这些源编译并导出到本地 XML 文档的主标题列表。

仔细考虑后,我决定创建一个实现 FlagsAttributeNewsCategory 枚举,这样类的使用者就可以将任何新闻类别的组合传递给 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 的主体中测试您的条件。我可能花在这方面的创造和书写上的时间比实际需要的要多,但这帮助我更深入地理解了如何使用位运算将多个值传递给类,并提取它们以对每个值执行特定操作。希望这在您未来的工作中能有所帮助。此外,如果您觉得本文中有任何错误或不准确之处,请不要犹豫告诉我。我喜欢反馈和学习新东西。

© . All rights reserved.