C# 在不声明每个可能的组合的情况下打开 Enum(带有 Flags 属性)?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1040731/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 06:46:10  来源:igfitidea点击:

Switch on Enum (with Flags attribute) without declaring every possible combination?

c#enumsswitch-statementflagsbit

提问by MartinF

how do i switch on an enum which have the flags attribute set (or more precisely is used for bit operations) ?

我如何打开设置了标志属性的枚举(或更准确地说是用于位操作)?

I want to be able to hit all cases in a switch that matches the values declared.

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

The problem is that if i have the following enum

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

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

and I want to use a switch like this

我想使用这样的开关

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;
}

If "theCheckType" is set to both CheckType.Form | CheckType.TempData I want it to hit both case's. Obviously it wont hit both in my example because of the break, but other than that it also fails because CheckType.Form is not equal to CheckType.Form | CheckType.TempData

如果 "theCheckType" 设置为 CheckType.Form | CheckType.TempData 我希望它同时满足这两种情况。显然,由于中断,它不会在我的示例中同时命中,但除此之外它也失败了,因为 CheckType.Form 不等于 CheckType.Form | 检查类型.TempData

The only solution then as I can see it is to make a case for every possible combination of the enum values ?

正如我所看到的,唯一的解决方案是为枚举值的每个可能组合做一个案例?

Something like

就像是

    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...

But that really isnt very desired (as it will quickly grow very big)

但这真的不是很想要(因为它会很快变得很大)

Right now i have 3 If conditions right after eachother instead

现在我有 3 个 If 条件,而不是紧随其后

Something like

就像是

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

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

....

But that also means that if i have an enum with 20 values it have to go through 20 If conditions every single time instead of "jumping" to only the needed "case"/'s as when using a switch.

但这也意味着,如果我有一个包含 20 个值的枚举,它必须每次都经历 20 个 If 条件,而不是像使用 switch 那样“跳转”到仅需要的“case”/'s。

Is there some magic solution to solve this problem?

是否有一些神奇的解决方案来解决这个问题?

I have thought of the possibility to loop through the declared values and then use the switch, then it would only hit the switch for each value declared, but I don't know how it will work and if it performance vice is a good idea (compared to a lot of if's) ?

我已经考虑过遍历声明的值然后使用开关的可能性,然后它只会为每个声明的值点击开关,但我不知道它将如何工作,以及它的性能缺陷是否是一个好主意(与很多 if 相比)?

Is there an easy way to loop through all the enum values declared ?

有没有一种简单的方法来循环所有声明的枚举值?

I can only come up with using ToString() and splitting by "," and then loop through the array and parse every single string.

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



UPDATE:

更新:

I see that i haven't done a good enough job explaining. My example is to simple (tried to simplify my scenario).

我发现我解释得还不够好。我的例子很简单(试图简化我的场景)。

I use it for a ActionMethodSelectorAttribute in Asp.net MVC to determine if a method should be available when resolving the url/route.

我将它用于 Asp.net MVC 中的 ActionMethodSelectorAttribute 来确定在解析 url/route 时方法是否应该可用。

I do it by declaring something like this on the method

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

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

That would mean that it should check if the Form or TempData have a key as specified for the method to be available.

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

The methods it will be calling (doSomething(), doSomethingElse() and doWhatever() in my previous example) will actually have bool as return value and will be called with a parameter (different collections that doesn't share a interface that can be used - see my example code in the link below etc).

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

To hopefully give a better idea of what i am doing i have pasted a simple example of what i am actually doing on pastebin - it can be found here http://pastebin.com/m478cc2b8

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

采纳答案by Jamie Ide

How about this. Of course the arguments and return types of DoSomething, etc., can be anything you like.

这个怎么样。当然,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;
                }
            }
        }
    }
}

回答by Rauhotz

What about a Dictionary<CheckType,Action>that you will fill like

Dictionary<CheckType,Action>你会喜欢的一个怎么样

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

a decomposition of your value

你的价值的分解

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

and then

进而

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

(code untested)

(代码未经测试)

回答by LBushkin

Flags enums can be treated as a simple integral type in which each individual bit corresponds to one of the flagged values. You can exploit this property to convert the bit-flagged enum value into an array of booleans, and then dispatch the methods you care about from a correlated array of delegates.

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

EDIT:We could certainly make this code more compact through the use of LINQ and some helper functions, but I think it's easier to understand in the less sophisticated form. This may be case where maintainability trumps elegance.

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

Here's an example:

下面是一个例子:

[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
  }
}

Alternatively, if the order in which you evaluate doesn't matter, here is simpler, clearer solution that works just as well. If order does matter, replace the bit-shifting operations with an array containing the flags in the order you want to evaluate them in:

或者,如果您评估的顺序无关紧要,这里有一个更简单、更清晰的解决方案,同样适用。如果顺序确实很重要,请用包含标志的数组替换位移操作,该数组按照您要评估它们的顺序:

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
}

回答by LukeH

Based on your edit and your real-life code, I'd probably update the IsValidForRequestmethod to look something like this:

根据您的编辑和现实生活中的代码,我可能会将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;
}

回答by Stijn Van Antwerpen

Just use HasFlag

只需使用HasFlag

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

回答by Joseph

The easiest way is to just perform an ORedenum, in your case you could do the following :

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

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

Once you have the enumsetup its now easy to perform your switchstatement.

完成enum设置后,现在可以轻松执行您的switch语句。

E.g, if i have set the following :

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

var chkType = CheckType.Form | CheckType.QueryString;

I can use the following switchstatement as follows :

我可以使用以下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;
}

Much cleaner and you don't need to use an ifstatement with HasFlag. You can make any combinations you want and then make the switch statement easy to read.

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

I would recommend breaking apart your enums, try see if you are not mixing different things into the same enum. You could setup multiple enumsto reduce the number of cases.

我建议将您enumsenum. 您可以设置多个enums以减少案例数量。

回答by Alex Sanséau

With C# 7 you can now write something like this:

使用 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;
    }
}

回答by justromagod

Should be possible in C# 7

在 C# 7 中应该是可能的

switch (t1)
    {
        case var t when t.HasFlag(TST.M1):
            {
                break;
            }
        case var t when t.HasFlag(TST.M2):
            {
                break;
            }

回答by MatthewOrionos

Cast it to its base type, the great thing about this is it tells you when there are duplicate values present.

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

[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;

}