Click here to Skip to main content
Click here to Skip to main content

Enums, Flags, and C# — Oh my! (bad pun…)

By , 3 Sep 2009
 

I’m not sure about everyone else, but I just love Enumerated types. What's more, I love the Flags Attribute when you combine them together. This post explores how you can use these two things along with Extension Methods to make your code more compact and easier to understand.

If you’ve never used this combination before, then you’re missing out. Consider the following code…

class User {
    bool CanDelete;
    bool CanRead;
    bool CanWrite;
    bool CanModify;
    bool CanCreate;
}

Okay, so that’s no big deal even though it may be quite a few extra lines of code. It would be nice to be able to combine all of those permissions into a single value. That’s where an Enumerated Type with a FlagAttribute comes in.

enum PermissionTypes : int {
    None = 0,
    Read = 1,
    Write = 2,
    Modify = 4,
    Delete = 8
    Create = 16,
    All = Read | Write | Modify | Delete | Create
}

//and the class from before
class User {
    PermissionTypes Permissions = PermissionTypes.None;
}

Excellent…. so now what?

So now, what's great about this is we can assign multiple values onto the same property. Not only that, we can also check for existing values with a (strange) comparison.

//create a new user
User admin = new User();
admin.Permissions = PermissionTypes.Read
    | PermissionTypes.Write
    | PermissionTypes.Delete;

//check for permissions
bool canRead = ((PermissionTypes.Read & admin.Permissions) == PermissionTypes.Read);
bool canWrite = ((PermissionTypes.Write & admin.Permissions) == PermissionTypes.Write);
bool canCreate = ((PermissionTypes.Create & admin.Permissions) == PermissionTypes.Create);

//and the results
Console.WriteLine(canRead); //true
Console.WriteLine(canWrite); //true
Console.WriteLine(canCreate); //false

Now shorter and easier to read — sorta. See that really odd comparison? That’s what you need to write each time you want to check for a value. It’s not that bad, but it isn’t really something I’d like to type very often. You could write a separate function to do these comparisons for you, but we can do even better than that.

Taking advantage of Extension Methods

Since an Enumerated Type isn’t really a class, you can’t extend methods onto them. However, you can extend methods onto the class System.Enum. Methods added to this class will appear in the methods for all of your enumerated types!

Here is an example method…

//full class included at the end of the post
public static class EnumerationExtensions {

    //checks to see if an enumerated value contains a type
    public static bool Has<T>(this System.Enum type, T value) {
        try {
            return (((int)(object)type & 
              (int)(object)value) == (int)(object)value);
        }
        catch {
            return false;
        }
    }
}

Now, this code does make an assumption that it can cast your Enumerated Type to an integer. You could do some type checking before you do the comparisons, but for the sake of this example, we’re going to keep this short.

So, just how do you use this extension?

//start with a value
PermissionTypes permissions = PermissionTypes.Read | PermissionTypes.Write;

//then check for the values
bool canRead = permissions.Has(PermissionTypes.Read); //true
bool canWrite = permissions.Has(PermissionTypes.Write); //true
bool canDelete = permissions.Has(PermissionTypes.Delete); //false

Now, that is much easier to read! Even better, you’ll notice despite the fact this has a Generic parameter, we don’t have to provide the type at the start since the method can infer it from the parameter (implicitly typed parameters — sweeeeet!).

And don’t forget, System.Enum isn’t the only class you can do this with, there are other classes (like System.Array, for example) that you can add your own extension methods to for surprising results!

As I stated before, this doesn't cover all cases of what you could expect - this code should be modified depending on how you plan to use it. For example, if you're using long, uint, ulong, this code won't cover all of your cases.

You may also wonder why we cast to an object before we cast to an int. When you're working with Generics, you can't cast to a value (non-nullable) type immediately, you have to either cast to an object and then to a value type, or just cast directly to a nullable value type, such as int.

Below is the full source code for the EnumerationExtensions class. If you have any improvements, please let me know! I'm currently working on a revised version to improve this code.

namespace Enum.Extensions {

    public static class EnumerationExtensions {

        //checks if the value contains the provided type
        public static bool Has<T>(this System.Enum type, T value) {
            try {
                return (((int)(object)type & (int)(object)value) == (int)(object)value);
            }
            catch {
                return false;
            }
        }

        //checks if the value is only the provided type
        public static bool Is<T>(this System.Enum type, T value) {
            try {
                return (int)(object)type == (int)(object)value;
            }
            catch {
                return false;
            }
        }

        //appends a value
        public static T Add<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type | (int)(object)value));
            }
            catch(Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not append value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }
        }

        //completely removes the value
        public static T Remove<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type & ~(int)(object)value));
            }
            catch (Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not remove value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License

About the Author

webdev_hb
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Question5member.NetStars6 Feb '13 - 22:04 
Well explained
GeneralMy vote of 5member.NetStars6 Feb '13 - 22:04 
Well explained!
GeneralMy vote of 5memberMBigglesworth798 Jun '11 - 21:28 
Very useful tip!
GeneralI think .NET 4 enum has some new stuffmvpSacha Barber9 Apr '11 - 21:45 
Like HasFlag which really helps you out. Still nice idea
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralMy vote of 5memberEddy Vluggen2 Feb '11 - 23:34 
A nice addition to the toolbox, making the comparisons a bit more readable - thanks for sharing Smile | :)
Generalnice onememberPranay Rana17 Dec '10 - 1:12 
good one like it
For any question : http://pranayamr.blogspot.com/
 
vote my article :

Learn SQL to LINQ ( Visual Representation )


Calling WCF Services using jQuery

GeneralMy vote of 5memberVarun Sareen19 Aug '10 - 0:10 
Never seen such a good work on Enum & Flags. The article have made many of my concepts clear regarding the enums.
 
Thanks to author Smile | :) cheers
GeneralCompile time type safety [modified]memberEspen Røvik Larsen10 Feb '10 - 22:07 
What about using generics for what it's worth in such a way that type safety is ensured between the two input parameters at compile time :
 
public bool Has(this T type, T value) where T : System.Enum
 

And since an enum is restricted to integral types (that there are not many of), it should not require much code change to make it work for the different types.
 
"The approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong." (enum (C# Reference[^])
 
The Implicit Numeric Conversions Table[^] shows that all these types cast implicitly to long, except ulong.
 
One can thus use Enum.GetUnderlyingType() and switch it against ulong and long (having different handling code for long and ulong).
 
switch(Enum.GetUnderlyingType(type.GetType())) : ulong, default
 
I don't know how much the cost is for casting byte, sbyte, short, ushort, int, uint to long (and this will happen often as most enum types is int). Maybe it is a less cost to have different handling code for every type:
 
switch(Enum.GetUnderlyingType(type.GetType())) : byte, sbyte, short, ushort, int, uint, long, ulong
GeneralRe: Compile time type safetymemberEspen Røvik Larsen11 Feb '10 - 8:13 
I realize C# can not have System.Enum as a constraint on generic parameters. Interfaces works fine - though System.Enum doesn't implement one single interface that makes it unique it does implement three different interfaces - this should be good enough :
 
public static bool Has(this T type, T value) where T : IComparable, IConvertible, IFormattable
GeneralWordy bit testmemberSAKryukov11 Jan '10 - 6:32 
Look at this check:
bool canRead = ((PermissionTypes.Read & admin.Permissions) == PermissionTypes.Read);
 
This is too wordy and violates DRY Principle (Do Not Repeat Yourself) to some extent.
Use:
bool canRead = (PermissionTypes.Read & admin.Permissions) > 0;
or
bool cannotRead = (PermissionTypes.Read & admin.Permissions) == 0;

 
Sergey A Kryukov

GeneralVery goodmemberAuroreC4 Oct '09 - 21:46 
Hello,
 
Yours extensions complete mines !!
 
Maybe you can add "parse" function. (I usually use it to read xml files).
 
public static T ParseEnum<T>(string valueToParse)
	{
		T returnValue = default(T);
		if (Enum.IsDefined(typeof(T), valueToParse))
		{
			returnValue = (T)Enum.Parse(typeof(T), valueToParse);
		}
		return returnValue;
	}
 
Hope, it can help.
 
Aurore
GeneralPerformance & Type checkmemberGildor_haohao8 Sep '09 - 21:10 
Your code is cool, but by boxing and unboxing enums you are making your code with poor performance. Furthermore, I cannot see the point in using generic types. Since your are using System.Enum, why not use it as the type of second parameter? When you use T with no constrain, I can pass anything in, even null.
 
Regards,
 
-Ken
GeneralException handling smellmemberNathan Allan8 Sep '09 - 6:09 
Cool trick, but I'm concerned about what you are doing with exception handling. Returning false in the case of an exception seems like a wonderfully good way to mask defects and shoot oneself in the foot. Furthermore, there is a significant performance hit with raising exceptions, so even if the error logic happened to work in some case, it could introduce a performance problem in inner loop code.
 
Regards,
 
-Nate
GeneralUmmmm.......membertonyt7 Sep '09 - 19:52 
http://www.codeproject.com/Messages/2838918/Something-new-modified.aspx[^]
GeneralRe: Ummmm.......memberwebdev_hb8 Sep '09 - 2:18 
Cool - I wasn't aware you had a comment out there about this same general idea. Good job!
GeneralGood onemvpN a v a n e e t h3 Sep '09 - 7:31 
This is good one. It would be good if you explain the rationale behind the numbers 0,1,2,4,8 etc. Also code is much more readable if you use bit-shift operators. So it will become
enum PermissionTypes : int {
    None = 0,
    Read = 1,
    Write = 1 << 1,
    Modify = 1 << 2,
    Delete = 1 << 3
    Create = 1 << 4,
    All = Read | Write | Modify | Delete | Create
}
I believe, above code conveys the intension in a much better way.
 
Smile | :)
 

Generalvery cool!memberDetoX833 Sep '09 - 6:56 
I didn't know that Enum can be extended! - 5 score from me
 
Small remark- I prefer:
 
MyENum |= Flag; // instead of Add()
GeneralFYI: extension method in VB.NET [modified]memberShimmy Weitzhandler17 Aug '09 - 22:51 
<Extension()> _
      Public Function BitwiseHas(Of TEnum As {IComparable, IConvertible, IFormattable})(ByVal [enum] As TEnum, ByVal value As TEnum) As Boolean
            Return (DirectCast([enum], Object) And value) = value
      End Function
 
      <Extension()> _
      Public Function BitwiseDisable(Of TEnum As {IComparable, IConvertible, IFormattable})(ByVal [enum] As TEnum, ByVal value As TEnum) As TEnum
            Return [enum] And Not DirectCast(value, Object)
      End Function
 
      <Extension()> _
      Public Function BitwiseEnable(Of TEnum As {IComparable, IConvertible, IFormattable})(ByVal [enum] As TEnum, ByVal value As TEnum) As TEnum
            Return [enum] Or DirectCast(value, Object)
      End Function
 
      Public Function GetMaxValue(Of TEnum As {IComparable, IConvertible, IFormattable})() As TEnum
            Dim type = GetType(TEnum)
            If Not type.IsSubclassOf(GetType([Enum])) Then Throw New InvalidCastException("Cannot cast '" &amp; type.FullName &amp; "' to System.Enum.")
            Return [Enum].ToObject(type, [Enum].GetValues(type).Cast(Of Integer).Last)
      End Function
 
Shimi
modified on Tuesday, September 8, 2009 5:19 PM

GeneralRe: FYI: extension method in VB.NETmemberHanzie537 Sep '09 - 21:47 
Thanks Shimi, I've been trying to do this without success so far.
GeneralRe: FYI: extension method in VB.NETmemberShimmy Weitzhandler8 Sep '09 - 11:23 
I edited my post.
it works for me.
 
I will be glad to hear about the problem you encounter.
 
Shimi

Questionmixing different enumtypes ?memberFrank Jurisch29 Jul '09 - 0:50 
elegant   Smile | :)
i guess there is a gap to mix types:
<code>
[TestMethod()]
public void HasTestFalseByType() {
     PermissionTypes value = PermissionTypes.Read   ;
     bool has = value.Has(AttributeTargets.Assembly );
     Assert.AreEqual(false, has);
}</code>
i expected that this would fail
 
casting to int (or long...) makes int and all enumvalues with same int
can awnser true
 
i think it will be little more complete while testing against typemix like this way:
 
<code>
public static void DontMixTypes<T>(this System.Enum type, T value) {
      if(!(type is T)) {
     throw new ArgumentException(
           string.Format(
"Could not mix with enumerated type '{0}'.", typeof(T).Name));
      }
}
</code>
 
i call this in Has, Add, Remove in the first line
<code>
type.DontMixTypes(value);
</code>
in method Is it returns false:
<code>
return ((type is T   )&&((int)(object)type == (int)(object)value));
</code>
 
i think this would be safer
my tests satisfied me
AnswerRe: mixing different enumtypes ?memberFrank Jurisch29 Jul '09 - 2:25 
think first.... Sigh | :sigh:
 
A)
after testing Has and Is i assumed the same behavior in Add and Remove
 
but
<code>
PermissionTypes value = PermissionTypes.Test124;
PermissionTypes expected = value.Remove(AttributeTargets.Assembly);
</code>
compiles never :CS0266(Cannot implicitly convert type 'type1' to 'type2'. An explicit conversion exists (are you missing a cast?) )
 
have to learn more deeply about generics
 
why the same signature behaves in these different ways ?
 
B)
Is and Has should use same semantic: both with or without exception against typemix
i use now false if not realy true instead of exception in Has also
 
C)
DontMixTypes is not nessesary in this way
a long way round
sorry
GeneralProblem with VB Port [modified]memberDisIsHoody24 Jul '09 - 5:01 
I'm trying to use this in VB.NET and for some reason it isn't working properly, however it isn't throwing an exception either. For example I have an enum type:
Public Enum SettingType as Integer
None=0
Sound=1
Archive=2
End Enum
 
I also have my variable Public SettingsFlag as SettingType. Now when my program loads I try to set the SoundFlag with SettingsFlag.Add(SettingType.Sound) however it won't add it. I added a watch to the SettingsFlag and it just remains the same. Any ideas? BTW great code article.
 
P.S. This is my VB.NET port of the Add function...I'm believe this is where something needs to be changed:
 <System.Runtime.CompilerServices.Extension()> _
        Public Function Add(Of T)(ByVal type As System.Enum, ByVal value As T) As T
        Try
            Return DirectCast(DirectCast(((CInt(DirectCast(type, Object)) Or CInt(DirectCast(value, Object)))), Object), T)
        Catch ex As Exception
            Throw New ArgumentException(String.Format("Could not append value from enumerated type '{0}'.", GetType(T).Name), ex)
        End Try
    End Function
~Dominick
 
modified on Friday, July 24, 2009 11:16 AM

GeneralCoolmemberSteve Wellens18 Jul '09 - 17:11 
Great post!
 
Steve Wellens

GeneralAn idea for further enhancementsmemberS. Töpfer14 Jul '09 - 2:18 
Hi, thank you for your article. It reminds me on my code which also uses lots of enums and I think more than one time how to improve the use of them.
 
I've got a few enums and every enum has an associated function which retrieves the length of the enum (I need to know how many different entries a enum has). The function looks always the same, only the name of the enums changes:
 

Public Shared Function GetLengthOfEnum() As Integer
Return System.Enum.GetValues(Type.GetType("TheNameOfMyEnum")).Length - 1
End Function
 
(Excuse me for using vb.net...)
 
Perhaps you have an idea how to integrate this into your extension class? I know your article deals with the instance of an enum and my problem lays on a more abstract level (maybe comparable to a static function of base type Integer or so).
 
Have a nice day,
Sebastian

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 3 Sep 2009
Article Copyright 2009 by webdev_hb
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid