Switch on Enum (with Flags attribute) without declaring every possible combination?
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;
}
}
Comments
-
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 almost 15 yearsThanks 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 almost 15 yearsThanks 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 almost 15 yearsThanks 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 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 almost 15 yearsMaybe not adequate for MartinF's problem, but very elegant... +1 ;)
-
iCeStar almost 15 yearsThanks 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 almost 15 yearsI 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 almost 15 yearsHave a look at this: stackoverflow.com/questions/1060760/…
-
iCeStar almost 15 yearsThanks 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 about 8 yearsEnum(...).GetValues returns an Array; you cannot use 'Where.
-
julealgon over 5 yearsWon'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 yourswitch
and then not process theQueryString
logic. -
julealgon over 5 yearsThis 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 over 5 yearsWhat if you need to perform a different logic with the
None
option (value0
)? Your second example stops the flag iteration when it reaches0
, but having enums with aNone = 0
option is a good practice and there could be extra logic tied to that. -
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 almost 5 yearsThis is by far the simplest solution. Should be higher up.
-
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 almost 4 yearsTo 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 over 3 yearscool option; interest steps will good to test perfomance
-
Manfred Wippel over 2 yearsThank you - this should be the accepted answer right now
-
KEMBL over 2 years@ezekg unfortunately,
break
is not optional if you have some code inside thecase
, sometimes it can be replaced withgoto
but I do not see how to use it for C# 7