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

40,911

Solution 1

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

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

Solution 2

Just use HasFlag

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

Solution 3

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.

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
}

Solution 4

Should be possible in C# 7

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

Solution 5

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

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;
    }
}
Share:
40,911
iCeStar
Author by

iCeStar

LinkedIn: https://www.linkedin.com/in/martinfrom/

Updated on May 20, 2020

Comments

  • iCeStar
    iCeStar about 4 years

    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

    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

    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.

    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) ?

    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.


    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.

    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.

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

    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

  • iCeStar
    iCeStar almost 15 years
    Thanks for the answer. I have updated my original post. I dont think it is possible to use a dictionary as i am declaring it in a attribute (see example) - or at least i dont know how it should be done.
  • iCeStar
    iCeStar almost 15 years
    Thanks for the answer. I have updated my original question. It seems like a nice solution, but unfortunatly the way my code is structured i need to get a return value from the method being called and also pass different type of parameters (i have linked to an example of what i am trying to do at the bottom). Hopefully the example code will explain my problem better than my writting. :)
  • iCeStar
    iCeStar almost 15 years
    Thanks for the answer. I dont see how this would be different from the "If" conditions. This would loop through every value in "map" (and every value in the CheckType enum would have to be registered in the "map" dictionary). If you mean that I should declare the Dictionary in the attribute, i dont think that is possible. I have updated my original question with a link to some example code of what i am actually trying to do, to hopefully clarify it better.
  • LukeH
    LukeH almost 15 years
    @MartinF, This is based on your updated example code! You're right that it isn't vastly different to your "if" conditions. The only advantage is that any new CheckType value only has to be included in the map.
  • Thomas Levesque
    Thomas Levesque almost 15 years
    Maybe not adequate for MartinF's problem, but very elegant... +1 ;)
  • iCeStar
    iCeStar almost 15 years
    Thanks for your reply. It is pretty much like Lukes solution just using the switch instead of the dictionary mapping. This seems to be the best solution though i have really hoped that it was possible somehow to only loop the values in "theCheckType" instead of looping the whole enum, as there really is no reason to check all the enum values, only the ones set in "theCheckType". But i guess that isnt possible.
  • Jamie Ide
    Jamie Ide almost 15 years
    I don't see the similarity to Luke's answer; I think mine is significantly simpler. As I see it, you've either got to check "theCheckType" against the enum or check the enum against "theCheckType". Either way you have to check all the possible values. If you declare your enum as long (Int64) you can obviously have a maximum of 64 values, so there's no need to worry about performance in such a short loop.
  • Jamie Ide
    Jamie Ide almost 15 years
  • iCeStar
    iCeStar almost 15 years
    Thanks again. What i mean about the similarity to Lukes answers is that they both do the same - loop through every value in the CheckType enum, and in his case call use dictionary and yours use the switch (which is actually behind the scenes as far as i know using some sort of implementation of a hashtable / dictionary). But i agree yours is simpler and probably the best solution (will mark it as an accepted answer) If i could only loop the values in "theCheckType" i could just call the switch directly, and it would only loop and call the switch for the number of times neccesary.
  • BillW
    BillW about 8 years
    Enum(...).GetValues returns an Array; you cannot use 'Where.
  • julealgon
    julealgon over 5 years
    Won't this hit only a single case and skip processing the rest? For example, if you have CheckType.Form | CheckType.QueryString, it will match the first expression on your switch and then not process the QueryString logic.
  • julealgon
    julealgon over 5 years
    This creates massive code duplication and does not scale properly. I would strongly discourage people from going that route unless the enum has like 2 options and is not expected to grow at all. Even then, I'd personally not go with it due to the code duplication aspect.
  • julealgon
    julealgon over 5 years
    What if you need to perform a different logic with the None option (value 0)? Your second example stops the flag iteration when it reaches 0, but having enums with a None = 0 option is a good practice and there could be extra logic tied to that.
  • ezekg
    ezekg over 5 years
    @julealgon yes, but that’s just how a switch case works. You can omit the break statement if you need to.
  • Slava Knyazev
    Slava Knyazev almost 5 years
    This is by far the simplest solution. Should be higher up.
  • Jamie Ide
    Jamie Ide almost 4 years
    @SlavaKnyazev I'm sure it would be except HasFlag was introduced in .NET 4.0 in 2010. SO is nearly useless in regards to which version(s) an answer applies to, and it's only going to get worse with the passage of time.
  • Zwander
    Zwander almost 4 years
    To be fair, you could always do the bitwise form of the above: if((theCheckType & CheckType.Form) == CheckType.Form) DoSomething (...); if((theCheckType & CheckType.QueryString) == CheckType.QueryString) DoSomethingElse (...); So it is a little surprising no one suggested the equivalent.
  • user1005462
    user1005462 over 3 years
    cool option; interest steps will good to test perfomance
  • Manfred Wippel
    Manfred Wippel over 2 years
    Thank you - this should be the accepted answer right now
  • KEMBL
    KEMBL over 2 years
    @ezekg unfortunately, break is not optional if you have some code inside the case, sometimes it can be replaced with goto but I do not see how to use it for C# 7