Click here to Skip to main content
15,867,488 members
Articles / Programming Languages / C#

Using a Generic Type to Simplify Flags Enumeration Operations

Rate me:
Please Sign up or sign in to vote.
4.77/5 (10 votes)
6 Dec 2010CPOL3 min read 39.1K   236   12   16
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>

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

MethodReturnsComment
Set()TSets all flags in the register. Returns the updated state of the register.
Set(T)TSet the specified flags. Use logical OR to set multiple flags. Returns the updated state of the register.
Clear()TClears all flags in the register. Returns the updated state of the register.
Clear(T)TClear the specified flags. Use logical OR to set multiple flags. Returns the updated state of the register.
IsSet(T)boolQuery 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.

PropertyTypeComment
StateTReturns 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.

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

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

C#
// 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:

C#
// 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.

C#
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...

C#
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)


Written By
United Kingdom United Kingdom
Nothing interesting to report.

Comments and Discussions

 
GeneralMy vote of 1 Pin
AndyHo6-Dec-10 1:04
professionalAndyHo6-Dec-10 1:04 
GeneralRe: My vote of 1 Pin
cigwork6-Dec-10 2:10
cigwork6-Dec-10 2:10 
GeneralRe: My vote of 1 [modified] Pin
AndyHo6-Dec-10 2:18
professionalAndyHo6-Dec-10 2:18 
GeneralRe: My vote of 1 Pin
PIEBALDconsult6-Dec-10 7:10
mvePIEBALDconsult6-Dec-10 7:10 
GeneralRe: My vote of 1 Pin
Philip Liebscher13-Dec-10 12:38
Philip Liebscher13-Dec-10 12:38 
GeneralRe: My vote of 1 Pin
cigwork6-Dec-10 2:50
cigwork6-Dec-10 2:50 
Andy, the code was offered as freely modifiable source code with known, advertised limitations and on the assumption that the audience here is quite capable of revising posted code if it doesn't quite meet their needs.
RantRe: My vote of 1 Pin
Pete Goodsall7-Dec-10 2:14
Pete Goodsall7-Dec-10 2:14 
Generalif you use a underlying type of long, ulong the system fails! Pin
AndyHo6-Dec-10 1:03
professionalAndyHo6-Dec-10 1:03 
GeneralRe: if you use a underlying type of long, ulong the system fails! Pin
cigwork6-Dec-10 2:07
cigwork6-Dec-10 2:07 
GeneralIn .NET 4.0 there is a HasFlag that helps a lot Pin
Sacha Barber3-Dec-10 21:39
Sacha Barber3-Dec-10 21:39 
GeneralRe: In .NET 4.0 there is a HasFlag that helps a lot Pin
cigwork4-Dec-10 0:05
cigwork4-Dec-10 0:05 
GeneralRe: In .NET 4.0 there is a HasFlag that helps a lot Pin
Sacha Barber4-Dec-10 20:26
Sacha Barber4-Dec-10 20:26 
GeneralThoughts Pin
PIEBALDconsult3-Dec-10 11:09
mvePIEBALDconsult3-Dec-10 11:09 
GeneralRe: Thoughts Pin
cigwork4-Dec-10 0:08
cigwork4-Dec-10 0:08 
GeneralMy vote of 5 Pin
Burak Ozdiken3-Dec-10 10:31
Burak Ozdiken3-Dec-10 10:31 
GeneralThank you! Pin
Toli Cuturicu3-Dec-10 10:29
Toli Cuturicu3-Dec-10 10:29 

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

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