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

Using a Generic Type to Simplify Flags Enumeration Operations

, 6 Dec 2010
Rate this:
Please Sign up or sign in to vote.
Using a generic type taking an enumerated type to provide simple manipulation of the enumeration as an alternative to [Flags] attribute

Introduction

The introduction of the [Flags] attribute made it very much easier to set up and use flag registers based on enumerated types. However, there are times when it can be cumbersome to query the state of individual flags or combinations of flags resulting in code that is difficult to understand without careful reading.

By providing a generic type that allows any enumeration, whether marked with the [Flags] attribute or not, to be used as a flag type with methods to clear, set and interrogate flag state, we can make it easier to use such types and make the code easier to understand and therefore easier to develop and maintain.

Because we can use enumerations not marked with the [Flags] attribute, we can use enumerations in third party DLLs, assuming it is appropriate and necessary, without having to wrap them in a marked in-house enumeration.

Using the Code

Outline

The developer defines an enumeration to meet the application's requirements. He or she then creates an instance of the generic type Register<Enumeration> with the desired enumerated type and uses that in place of a variable of the enumerated type.

Class : Register<Enumeration>

Constructor Comment
Register<T>() Creates an instance based on enumerated type T with all flags cleared.
Register<T>(T) Creates an instance based on enumerated type T with the specified flags set and the remainder clear. Use logical OR to set multiple flags.

Method Returns Comment
Set() T Sets all flags in the register. Returns the updated state of the register.
Set(T) T Set the specified flags. Use logical OR to set multiple flags. Returns the updated state of the register.
Clear() T Clears all flags in the register. Returns the updated state of the register.
Clear(T) T Clear the specified flags. Use logical OR to set multiple flags. Returns the updated state of the register.
IsSet(T) bool Query the state of a flag in the register. Multiple flags can be queried Using logical OR. Returns false if one or more of the queried flags is clear.

Property Type Comment
State T Returns the current state of the register as the instantiating enumerated type.

Using : Register<Enumeration>

Example

Define our enumeration. Note that we no longer need to mark this with the [Flags] attribute.

public enum Greek
{
    Alpha   = 1,
    Beta    = 2,
    Gamma   = 4,
    Delta   = 8,
    Epsilon = 16,
}

Now create an instance of the Register using the appropriate enumeration:

// We can either create an instance with all flags clear...
Register<Greek> startClear = new Register<Greek>();
 
// ... or create a flag register with one or more flags set.
Register<Greek> startSet = new Register<Greek>(Greek.Delta | Greek.Gamma);

And use it as needed:

// Set all flags in a single operation
startClear.Set();
 
// Clear flags either singly or in combination.
startClear.Clear(Greek.Alpha);
startClear.Clear(Greek.Gamma|Greek.Epsilon);
 
// Set flags either singly or in combination.
startClear.Set(Greek.Alpha);
startClear.Set(Greek.Gamma|Greek.Epsilon);
 
// Querying the state of an individual flag
Console.WriteLine("{0} set {1}", Greek.Alpha, startClear.IsSet(Greek.Alpha));
 
// Querying multiple flags in one operation.
Register<greek> startSet = new Register<greek>(Greek.Delta | Greek.Gamma);
Console.WriteLine("{0} set {1}",
	Greek.Alpha.ToString() + Greek.Delta.ToString(),
	startSet.IsSet(Greek.Alpha|Greek.Delta));	// Alpha flag is clear 
						// so returns false.
 
Console.WriteLine("{0} set {1}",
	Greek.Gamma.ToString() + Greek.Delta.ToString(),
	startSet.IsSet(Greek.Gamma|Greek.Delta)); 	// Both flags set returns true

We can also see that we have type safety if we try and assign the state of our Register<T> instance to another enumerated type.

Greek g  = startClear.State;   // All hunky dory.
Island i = startClear.State;   // Compile time error.

Notes

Internally the enumeration is held as int32. This reduces the amount of casting required to persuade the compiler that type <T> allows bitwise operations. We cast back to <T> only if a return value is required.

For most cases where a flag register is required, int32 is probably overkill, but because we control the internal representation of the register, we could implement it any way we chose.

It would have been nice to be able to constrain the type parameter <T> to allow the use of only enumerated types, e.g. class Y<T> where T : enum unfortunately no such constraint exists. This means that a developer could instantiate a Register<T> with pretty much any type so running the risk of a runtime error. The following will, alas, compile...

Register<string> r = new Register<string>();

...you have been warned.

Why?

Decades as a software developer creating and maintaining applications have made me appreciate anything, even something as trivial as reducing the effort required to manipulate bit flags, that makes it easier for me to write code that is correct the first time around and also allows anyone who inherits responsibility for the code to easily understand what I was trying to do when (inevitably) the business rules change and the code has to be revised in short order and with no access to the original design documentation.

Update

You might also like to have a look at an earlier article that covers the same ground and provides a workaround for constraint problem : EnumOperators

History

  • November 2010 - Convert type specific 1.1 code to type generic 3.5 code
  • November 2010 - Add link to PIEBALDConsults article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

cigwork

United Kingdom United Kingdom
Nothing interesting to report.

Comments and Discussions

 
GeneralMy vote of 1 PinmemberAndyHo6-Dec-10 1:04 
GeneralRe: My vote of 1 Pinmembercigwork6-Dec-10 2:10 
GeneralRe: My vote of 1 [modified] PinmemberAndyHo6-Dec-10 2:18 
GeneralRe: My vote of 1 PinmvpPIEBALDconsult6-Dec-10 7:10 
GeneralRe: My vote of 1 PinmemberPhilip Liebscher13-Dec-10 12:38 
GeneralRe: My vote of 1 Pinmembercigwork6-Dec-10 2:50 
RantRe: My vote of 1 PinmemberPete Goodsall7-Dec-10 2:14 
Agreed! The whole point of this site is to take what you want/need and modify it as necessary (or gain understanding of algorithms / techniques). Andy Ho - just make the underlying type 64 bit and stop needlessly whining! Poke tongue | ;-P
Generalif you use a underlying type of long, ulong the system fails! PinmemberAndyHo6-Dec-10 1:03 
GeneralRe: if you use a underlying type of long, ulong the system fails! Pinmembercigwork6-Dec-10 2:07 
GeneralIn .NET 4.0 there is a HasFlag that helps a lot PinmvpSacha Barber3-Dec-10 21:39 
GeneralRe: In .NET 4.0 there is a HasFlag that helps a lot Pinmembercigwork4-Dec-10 0:05 
GeneralRe: In .NET 4.0 there is a HasFlag that helps a lot PinmvpSacha Barber4-Dec-10 20:26 
GeneralThoughts PinmvpPIEBALDconsult3-Dec-10 11:09 
GeneralRe: Thoughts Pinmembercigwork4-Dec-10 0:08 
GeneralMy vote of 5 Pinmemberburak2993-Dec-10 10:31 
GeneralThank you! PinmemberToli Cuturicu3-Dec-10 10:29 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140709.1 | Last Updated 6 Dec 2010
Article Copyright 2010 by cigwork
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid