启用Enum(带有Flags属性),但未声明所有可能的组合吗?

问题描述 投票:47回答:9

我如何打开设置了flags属性的枚举(或更确切地说,用于位操作)?

我希望能够在与声明的值匹配的开关中处理所有情况。

问题是,如果我有以下枚举

[Flags()]public enum CheckType
{
    Form = 1,   
    QueryString = 2,
    TempData = 4,
}

而且我想使用这样的开关

switch(theCheckType)
{
   case CheckType.Form:
       DoSomething(/*Some type of collection is passed */);
       break;

   case CheckType.QueryString:
       DoSomethingElse(/*Some other type of collection is passed */);
       break;

   case CheckType.TempData
       DoWhatever(/*Some different type of collection is passed */);
       break;
}

如果“ theCheckType”设置为两个CheckType.Form | CheckType.TempData我希望它能同时击中两种情况。显然,由于中断,它不会在我的示例中同时击中,但除此之外,由于CheckType.Form不等于CheckType.Form |,它也会失败。 CheckType.TempData

然后,我看到的唯一解决方案是为枚举值的每种可能的组合辩护?

类似

    case CheckType.Form | CheckType.TempData:
        DoSomething(/*Some type of collection is passed */);
        DoWhatever(/*Some different type of collection is passed */);
        break;

    case CheckType.Form | CheckType.TempData | CheckType.QueryString:
        DoSomething(/*Some type of collection is passed */);
        DoSomethingElse(/*Some other type of collection is passed */);
        break;

... and so on...

但是确实不是很想要的(因为它将很快变得很大)

现在我有3个条件,而彼此都紧随其后

类似

if ((_CheckType & CheckType.Form) != 0)
{
    DoSomething(/*Some type of collection is passed */);
}

if ((_CheckType & CheckType.TempData) != 0)
{
    DoWhatever(/*Some type of collection is passed */);
}

....

但是这也意味着,如果我有一个带有20个值的枚举,则每次必须经过20个If条件,而不是像使用开关时那样仅“跳转”到所需的“ case /”。

有解决这个问题的魔术方法吗?

我曾考虑过可能循环遍历声明的值,然后使用开关,然后它将仅对每个声明的值命中该开关,但是我不知道它将如何工作,以及它是否对性能不利。想法(与很多if相比)?

是否有一种简单的方法可以遍历声明的所有枚举值?

我只能使用ToString()并用“,”分割,然后遍历数组并解析每个字符串。


更新:

我看到我的解释还不够好。我的示例很简单(试图简化我的方案)。

我将它用于Asp.net MVC中的ActionMethodSelectorAttribute,以确定在解析URL /路由时方法是否应该可用。

我通过在方法上声明这样的东西来做到这一点

[ActionSelectorKeyCondition(CheckType.Form | CheckType.TempData, "SomeKey")]
public ActionResult Index()
{
    return View();
} 

这意味着它应该检查Form或TempData是否具有为该方法指定的键。

它将被调用的方法(在我之前的示例中为doSomething(),doSomethingElse()和doWhatever())实际上将bool作为返回值,并且将使用参数(不共享接口的不同集合进行调用)可以使用-在下面的链接中查看我的示例代码,等等。

[为了希望更好地了解我在做什么,我在粘贴框上粘贴了我实际上在做什么的简单示例-可以在这里找到http://pastebin.com/m478cc2b8

c# enums switch-statement flags bit
9个回答
49
投票

怎么样。当然,DoSomething的参数和返回类型等可以是您喜欢的任何东西。

class Program
{
    [Flags]
    public enum CheckType
    {
        Form = 1,
        QueryString = 2,
        TempData = 4,
    }

    private static bool DoSomething(IEnumerable cln)
    {
        Console.WriteLine("DoSomething");
        return true;
    }

    private static bool DoSomethingElse(IEnumerable cln)
    {
        Console.WriteLine("DoSomethingElse");
        return true;
    }

    private static bool DoWhatever(IEnumerable cln)
    {
        Console.WriteLine("DoWhatever");
        return true;
    }

    static void Main(string[] args)
    {
        var theCheckType = CheckType.QueryString | CheckType.TempData;
        var checkTypeValues = Enum.GetValues(typeof(CheckType));
        foreach (CheckType value in checkTypeValues)
        {
            if ((theCheckType & value) == value)
            {
                switch (value)
                {
                    case CheckType.Form:
                        DoSomething(null);
                        break;
                    case CheckType.QueryString:
                        DoSomethingElse(null);
                        break;
                    case CheckType.TempData:
                        DoWhatever(null);
                        break;
                }
            }
        }
    }
}

18
投票

只需使用HasFlag

if(theCheckType.HasFlag(CheckType.Form)) DoSomething(...);
if(theCheckType.HasFlag(CheckType.QueryString)) DoSomethingElse(...);
if(theCheckType.HasFlag(CheckType.TempData)) DoWhatever(...);

13
投票

Flags枚举可被视为简单的整数类型,其中每个单个位对应于标记值之一。您可以利用此属性将位标记的枚举值转换为布尔数组,然后从相关的委托数组中分派您关心的方法。

EDIT: 我们当然可以通过使用LINQ和一些辅助函数来使此代码更紧凑,但是我认为以较不复杂的形式更容易理解。在这种情况下,可维护性胜过优雅。

这里是一个例子:

[Flags()]public enum CheckType
{
  Form = 1,       
  QueryString = 2,
  TempData = 4,
}

void PerformActions( CheckType c )
{
  // array of bits set in the parameter {c}
  bool[] actionMask = { false, false, false };
  // array of delegates to the corresponding actions we can invoke...
  Action availableActions = { DoSomething, DoSomethingElse, DoAnotherThing };

  // disassemble the flags into a array of booleans
  for( int i = 0; i < actionMask.Length; i++ )
    actionMask[i] = (c & (1 << i)) != 0;

  // for each set flag, dispatch the corresponding action method
  for( int actionIndex = 0; actionIndex < actionMask.Length; actionIndex++ )
  {
      if( actionMask[actionIndex])
          availableActions[actionIndex](); // invoke the corresponding action
  }
}

或者,如果您评估的顺序无关紧要,那么这里的解决方案更简单,更清晰,同样适用。如果顺序很重要,请使用包含标志的数组替换位移操作,该标志的顺序如下:

int flagMask = 1 << 31; // start with high-order bit...
while( flagMask != 0 )   // loop terminates once all flags have been compared
{
  // switch on only a single bit...
  switch( theCheckType & flagMask )
  {
   case CheckType.Form:
     DoSomething(/*Some type of collection is passed */);
     break;

   case CheckType.QueryString:
     DoSomethingElse(/*Some other type of collection is passed */);
     break;

   case CheckType.TempData
     DoWhatever(/*Some different type of collection is passed */);
     break;
  }

  flagMask >>= 1;  // bit-shift the flag value one bit to the right
}

4
投票

您将填写的Dictionary<CheckType,Action>怎么样?>

dict.Add(CheckType.Form, DoSomething);
dict.Add(CheckType.TempDate, DoSomethingElse);
...

您的价值分解

flags = Enum.GetValues(typeof(CheckType)).Where(e => (value & (CheckType)e) == (CheckType)e).Cast<CheckType>();

然后

foreach (var flag in flags)
{
   if (dict.ContainsKey(flag)) dict[flag]();
}

(未经测试的代码)


3
投票

使用C#7,您现在可以编写如下内容:


3
投票

在C#7中应该可能


1
投票

根据您的编辑和您的真实代码,我可能会更新IsValidForRequest方法,使其看起来像这样:


0
投票

将其声明为基本类型,这很妙,它告诉您何时它们是重复值。


-2
投票

最简单的方法是只执行一个ORed枚举,您可以执行以下操作:

© www.soinside.com 2019 - 2024. All rights reserved.