56

我如何打开一个设置了 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 条件紧随其后

就像是

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 作为返回值,并将使用参数调用(不共享接口的不同集合可以是使用 - 请参阅下面链接中的示例代码等)。

为了希望更好地了解我在做什么,我粘贴了一个简单的例子,说明我在 pastebin 上实际做了什么——可以在这里找到http://pastebin.com/m478cc2b8

4

9 回答 9

55

这个怎么样。当然,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;
                }
            }
        }
    }
}
于 2009-07-06T23:38:37.023 回答
38

只需使用HasFlag

if(theCheckType.HasFlag(CheckType.Form)) DoSomething(...);
if(theCheckType.HasFlag(CheckType.QueryString)) DoSomethingElse(...);
if(theCheckType.HasFlag(CheckType.TempData)) DoWhatever(...);
于 2016-12-12T10:24:52.363 回答
14

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

编辑: 我们当然可以通过使用 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
}
于 2009-06-24T20:36:02.207 回答
11

在 C# 7 中应该是可能的

switch (t1)
    {
        case var t when t.HasFlag(TST.M1):
            {
                break;
            }
        case var t when t.HasFlag(TST.M2):
            {
                break;
            }
于 2018-09-12T08:04:26.057 回答
6

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

public void Run(CheckType checkType)
{
    switch (checkType)
    {
        case var type when CheckType.Form == (type & CheckType.Form):
            DoSomething(/*Some type of collection is passed */);
            break;

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

        case var type when CheckType.TempData == (type & CheckType.TempData):
            DoWhatever(/*Some different type of collection is passed */);
            break;
    }
}
于 2018-06-06T11:49:46.530 回答
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]();
}

(代码未经测试)

于 2009-06-24T20:35:10.243 回答
2

将其转换为其基本类型,这样做的好处是它会告诉您何时存在重复值。

[Flags]
public enum BuildingBlocks_Property_Reflection_Filters
{
    None=0,
    Default=2,
    BackingField=4,
    StringAssignment=8,
    Base=16,
    External=32,
    List=64,
    Local=128,
}

switch ((int)incomingFilter)
{
    case (int)PropFilter.Default:
        break;
    case (int)PropFilter.BackingField:
        break;
    case (int)PropFilter.StringAssignment:
        break;
    case (int)PropFilter.Base:
        break;
    case (int)PropFilter.External:
        break;
    case (int)PropFilter.List:
        break;
    case (int)PropFilter.Local:
        break;
    case (int)(PropFilter.Local | PropFilter.Default):
        break;

}
于 2020-05-19T10:19:47.573 回答
1

根据您的编辑和实际代码,我可能会将IsValidForRequest方法更新为如下所示:

public sealed override bool IsValidForRequest
    (ControllerContext cc, MethodInfo mi)
{
    _ControllerContext = cc;

    var map = new Dictionary<CheckType, Func<bool>>
        {
            { CheckType.Form, () => CheckForm(cc.HttpContext.Request.Form) },
            { CheckType.Parameter,
                () => CheckParameter(cc.HttpContext.Request.Params) },
            { CheckType.TempData, () => CheckTempData(cc.Controller.TempData) },
            { CheckType.RouteData, () => CheckRouteData(cc.RouteData.Values) }
        };

    foreach (var item in map)
    {
        if ((item.Key & _CheckType) == item.Key)
        {
            if (item.Value())
            {
                return true;
            }
        }
    }
    return false;
}
于 2009-06-24T22:45:39.247 回答
-2

最简单的方法是执行ORed枚举,在您的情况下,您可以执行以下操作:

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

一旦你有了enum设置,现在很容易执行你的switch声明。

例如,如果我设置了以下内容:

var chkType = CheckType.Form | CheckType.QueryString;

我可以使用以下switch语句如下:

switch(chkType){
 case CheckType.Form:
   // Have Form
 break;
 case CheckType.QueryString:
   // Have QueryString
 break;
 case CheckType.TempData:
  // Have TempData
 break;
 case CheckType.FormQueryString:
  // Have both Form and QueryString
 break;
 case CheckType.QueryStringTempData:
  // Have both QueryString and TempData
 break;
 case CheckType.All:
  // All bit options are set
 break;
}

更干净,您不需要使用if带有HasFlag. 您可以进行任何您想要的组合,然后使 switch 语句易于阅读。

我建议拆开你的enums,试着看看你是否没有把不同的东西混在一起enum。您可以设置多个enums以减少案例数量。

于 2017-11-04T20:30:04.260 回答