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

13

60

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

Princely answered 24/6, 2009 at 20:18 Comment(0)
K
60

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;
                }
            }
        }
    }
}
Kaliningrad answered 6/7, 2009 at 23:38 Comment(4)
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.Princely
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.Kaliningrad
Have a look at this: #1061260Kaliningrad
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.Princely
F
48

Just use HasFlag

if(theCheckType.HasFlag(CheckType.Form)) DoSomething(...);
if(theCheckType.HasFlag(CheckType.QueryString)) DoSomethingElse(...);
if(theCheckType.HasFlag(CheckType.TempData)) DoWhatever(...);
Fraternal answered 12/12, 2016 at 10:24 Comment(3)
This is by far the simplest solution. Should be higher up.Michiko
@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.Kaliningrad
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.Parmenides
S
19

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;
            }
Smokedry answered 12/9, 2018 at 8:4 Comment(6)
cool option; interest steps will good to test perfomanceElohim
Thank you - this should be the accepted answer right nowReorganization
This will still hit only 1 case, and you can't omit breakSudoriferous
@OrestisP. by definition a switch statement will only match one case; even in the scenario that one case falls through to the next only one case will have been matched. If you want to match multiple cases you need to use a series of if clauses.Adolphadolphe
@AndrewH I agree, although I wouldn't go as far as saying it is by definition since some languages like Perl do allow multiple case blocks to be executed. In any case, this is the reason why you can't omit break when using the new C# 7 syntax, and why this answer shouldn't be the accepted answer.Sudoriferous
This will not go through all the cases, only the first match.Fraternal
E
14

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
}
Espalier answered 24/6, 2009 at 20:36 Comment(3)
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. :)Princely
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.Stickle
Downvote, because I tested your second snippet and is wrong. I'll post and updated and fixed question.Knucklebone
B
8

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;
    }
}
Burgess answered 6/6, 2018 at 11:49 Comment(3)
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.Stickle
@Stickle yes, but that’s just how a switch case works. You can omit the break statement if you need to.Mendel
@Mendel 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# 7Lalapalooza
S
5

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

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)

Stocktaking answered 24/6, 2009 at 20:35 Comment(3)
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.Princely
Maybe not adequate for MartinF's problem, but very elegant... +1 ;)Barbaraanne
Enum(...).GetValues returns an Array; you cannot use 'Where.Spithead
R
2

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;

}
Revulsive answered 19/5, 2020 at 10:19 Comment(0)
P
1

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

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;
}
Pimply answered 24/6, 2009 at 22:45 Comment(3)
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.Princely
@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.Pimply
I'm still not entirely clear on why you think your "if" statements are a problem. If you're trying to avoid them for performance reasons then that sounds very much like a premature optimisation to me.Pimply
B
0

I know this has already been solved, and there are several good answers here, but I really was wanting the same thing that the OP was asking for.

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

Of course, Jamie's accepted solution does it nicely, and there are a few other interesting answers, but I really wanted to get something a little more general and natural to use, so here's what I came up with:

public static TFlags[] Split<TFlags>(this TFlags flags) // , bool keepUndefined)
    where TFlags : Enum
{
    ulong valueToSplit = Convert.ToUInt64(flags);
    TFlags[] definedFlags = (TFlags[])Enum.GetValues(typeof(TFlags));
    List<TFlags> resultList = new List<TFlags>();

    if (valueToSplit == 0)
    {
        TFlags flagItem = 
            Array.Find(definedFlags, flag => Convert.ToUInt64(flag) == 0x00);
        resultList.Add(flagItem);
    }

    ulong checkBitValue = 1L;
    while (valueToSplit != 0)
    {
        ulong checkValue = checkBitValue & valueToSplit;
        if (checkValue != 0)
        {
            valueToSplit &= ~checkValue;

            TFlags flagItem =
                Array.Find(definedFlags, flag => Convert.ToUInt64(flag) == checkValue);

            // undefined flag, need a way to convert integer to TFlags
            if (Convert.ToUInt64(flagItem) != checkValue)
            {
                //if (keepUndefined)
                //    resultList.Add(checkValue);                        
                continue;
            }

            resultList.Add(flagItem);
        }

        checkBitValue <<= 1;
    }

    return resultList.ToArray();
}

(This code has been marginally tested and is working well for me so far.)

Issues converting between TFlags and integer types

I never could find a way to force a cast of an arbitrary integer value, even checking for and matching the underlying type. It always gives me an error converting integer to TFlags.

I'm assuming it's something to do with it being a generic type parameter instead of the real type. I feel like there's probably something obvious and I'll be kicking myself when I find it.

A possible solution is to have the caller supply a function that will cast it for you; but that seemed to go against trying to keep it generic and usable. You could put it in as an default null parameter and if it's passed in then that indicates you want to keep the undefined flags.

Fortunately it works out okay the other way around, I think you can cast from pretty much any integer type to an Enum, otherwise this would have been much harder. That way I was able to just use ulong and not worry about having to get the underlying type and doing some big switch statement to get a properly-size integer.

Usage

So, the idea is just that this way you do a Split() on the flags value you need to take action on, and it gives you an array of all the defined ones so you can just loop and do a switch as usual. LBushkin mentioned ordering, there's no attempt to do that here other than that these are in ascending order by flag value.

I actually think the original code in the post wasn't so bad notionally. It seemed like there was an aversion to doing something like in the accepted answer where you loop over all the possible flags and switch. But, in most circumstances, a loop and a switch should be pretty fast for the flags that aren't used, so unless this is running in a tight loop it's probably fine for most cases.

But I did like the idea of making it where you can do something more natural.

public DoTheChecks(CheckType typesToCheck)
{
    CheckType[] allCheckTypes = typesToCheck.Split();

    foreach (CheckType singleCheck in allCheckTypes)
        switch (singleCheck)
        {
            case CheckType.Form:
                CheckTheForms();
                break;

And so forth.

Final Notes

LBushkin's post, which also is going through the bitmap to figure out what flags have been set, has a comment asking about accounting for a None = 0 flag. I added in a check so that if the flag comes in as zero, we check for a default flag and add it if it exists.

This code does not check for the Flags() attribute on the Enum, so it's possible to pass in a non-flags enum which likely will not produce the expected results. Probably it would be worth checking for that and throwing an exception. I don't see any reason to implement something like this for non-flag enums, but the language doesn't really give much way to deal with it that I know of. The worst thing that should happen if someone does is they will get garbage back.

Other than being able to set an arbitrary undefined flag value, this does pretty much everything I wanted. It took me a really long time to figure out how to do everything, though. You can tell they never meant for you to work with Enums like this.

Bade answered 15/5, 2022 at 20:20 Comment(0)
K
0

I like LBushkin approach, but it didn't work.

1 << 31 evaluates to a negative number, so a while loop will be infinite.

I don't like flagMask != 0, I prefer to use flagMask >= 1 inside a for loop.

using System;
                    
public class Program
{
    [Flags]
    public enum test {
        a = 1 << 0, // 1
        b = 1 << 1, // 2
        c = 1 << 2, // 4
        d = 1 << 3, // 8
        e = 1 << 4, // 16
        f = 1 << 5, // 32
        g = 1 << 6, // 64
        h = 1 << 7 // 128       
    }
    
    public static void Main()
    {
        debug(test.a | test.b | test.h);
    }
    
    public static void debug(test t) {
        Console.WriteLine((int)t);
        
        for(var j = 1 << 30; j >= 1; j >>= 1) {
            var k = (test)((int)t & j);
            if((int)k != 0)
                Console.WriteLine(k);
        }
    }
}

Here is the fiddle: https://dotnetfiddle.net/OnZONC

Knucklebone answered 7/12, 2022 at 13:32 Comment(0)
V
0

Using the example of the StringSplitOptions enum type, which has the Flags attribute.

Switch design will look like this:

internal class Program {
  [Flags]
  private enum StringSplitOptions { None = 0, RemoveEmptyEntries = 1, TrimEntries = 2 }

  private static void Test(StringSplitOptions options = StringSplitOptions.None) {
    switch (options) {
      case StringSplitOptions.RemoveEmptyEntries: {
        Console.WriteLine($"Only {StringSplitOptions.RemoveEmptyEntries} case");
        break;
      }
      case StringSplitOptions.TrimEntries: {
        Console.WriteLine($"Only {StringSplitOptions.TrimEntries} case");
        break;
      }
      case (StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries): {
        Console.WriteLine($"Both {StringSplitOptions.RemoveEmptyEntries} and {StringSplitOptions.TrimEntries} cases");
        break;
      }
      default: {
        Console.WriteLine("Default case");
        break;
      }
    }
  }

  static void Main(string[] args) {
    Test(StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  }
}
Vaillancourt answered 12/10, 2023 at 14:41 Comment(0)
P
0

While this is obviously quite late to the party, for anyone else who may end up here while wanting to do the same, but through the use of Switch Expressions, here's an example for that:

[Flags()]public enum CheckType
{
    Form = 0x01,   
    QueryString = 0x02,
    TempData = 0x04,
    FormQueryString = 0x03, // Form | QueryString,
    QueryStringTempData = 0x06 // QueryString | TempData,
    All = 0xffffffff // All bits set!
}

#region Extras
struct FormData 
{
    string Name = "";
    string[] Data;
}

public sealed class ProcessedData
{
    private readonly List<string> _data = new();
    public string Name {get; set; } = string.Empty;
    public List<string> Data { get => this._data; }

    public ProcessedData( string name, params string[] data )
    {
        Name = string.IsNullOrWhiteSpace( name ) ? "Unknown" : name;
        Data.AddRange( data );
    }

    public ProcessedData( string name, IEnumerable<string> data )
    {
        Name = string.IsNullOrWhiteSpace( name ) ? "Unknown" : name;
        Data.AddRange( data );
    }
}
#endregion

#region Data Processing Functions
ProcessedData DoSomething( FormData form ) => new( form.Name, form.Data );

ProcessedData DoSomethingElse( string name, params string[] form ) => new( name, form );

ProcessedData DoWhatever( string name ) => new( name, File.ReadAllLines( name ) );
#endregion

#region The Example!
ProcessedData MyFormProcess( FormData source, CheckType theCheckType )
{
    ProcessedData _result = null;

    foreach ( var t in Enum.GetValues<CheckType>() )
        if ( _result is null && theCheckType.HasFlag( t ) )
            _result = switch t 
            {
                CheckType.Form => DoSomething( source ),
                CheckType.QueryString => DoSomethingElse( Form.Name, Form.Data ),
                CheckType.TempData => DoWhatever( Form.Name );
                _ => null, /* Default action -> this shouldn't be possible! */
            }

    return _result;
}
#endregion
Plank answered 6/12, 2023 at 23:32 Comment(0)
N
-2

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

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

Once you have the enum setup its now easy to perform your switch statement.

E.g, if i have set the following :

var chkType = CheckType.Form | CheckType.QueryString;

I can use the following switch statement as follows :

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 if statement with HasFlag. You can make any combinations you want and then make the switch statement easy to read.

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

Necrose answered 4/11, 2017 at 20:30 Comment(1)
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.Stickle

© 2022 - 2024 — McMap. All rights reserved.