EnumOperators






4.50/5 (10 votes)
A class to help ease the burden of not being able to specify enum as a generic constraint
Background
Given two values of an enumeration, we can perform certain operations on them. Basically, these operations are limited to equality operations and bitwise operations. Mathematical operations (if they make sense in your application) require casting to the underlying type, performing the operation, and casting back to the enumeration type.
A problem arises when we write a generic class and we want to use an enumeration as a generic parameter. The enum
keyword is not allowed as a generic constraint (nor is System.Enum
), so inside the generic class we can't perform the operations that are usually available. System.Enum
implements System.IComparable
so we can use that as a constraint which will allow us to perform equality operations.
if ( T1.CompareTo ( T2 ) == 0 ) ...
That may be enough for many classes, but some classes that use enumerations will need to perform bitwise or mathematical operations. For these, we'll have to convert to another type and back. But which type?
The underlying type of an enumeration can be any of [ byte
, sbyte
, short
, ushort
, int
, uint
, long
, ulong
] and none of these can contain the full range of all the others, so picking one and using it in a generic class is not the best solution. Fortunately, the System.Enum.GetUnderlyingType
method will let us know which type should be used. The task then is to implement methods that perform each operation on each possible underlying type, and be able to select the appropriate set of methods.
This article describes my EnumOperators
class, which is my solution to this situation.
EnumOperators
This is a generic class that takes an enumeration as a generic parameter. We can't constrain that parameter to enum
, but we can constrain it to System.IComparable
.
public static partial class EnumOperators<T>
where T : System.IComparable
{
}
An enumeration and delegates will help with selecting the appropriate set of methods to use.
private enum SupportedType
{
Byte
,
SByte
,
Int16
,
UInt16
,
Int32
,
UInt32
,
Int64
,
UInt64
}
public delegate T DelegateT ( T Op1 , T Op2 ) ;
public delegate bool DelegateBool ( T Op1 , T Op2 ) ;
We'll need some fields to store the underlying type and delegates.
public static readonly System.Type UnderlyingType ;
public static readonly DelegateT Add ;
public static readonly DelegateT Subtract ;
public static readonly DelegateT Multiply ;
public static readonly DelegateT Divide ;
public static readonly DelegateT Power ;
public static readonly DelegateT And ;
public static readonly DelegateT Or ;
public static readonly DelegateT Xor ;
The Constructor
The constructor simply checks that the generic parameter is an enumeration, then accesses the underlying type and uses it to map the correct methods.
static EnumOperators
(
)
{
System.Type basetype = typeof(T) ;
if ( !basetype.IsEnum )
{
throw ( new System.ArgumentException ( "T must be an Enum" ) ) ;
}
UnderlyingType = System.Enum.GetUnderlyingType ( basetype ) ;
switch
(
(SupportedType) System.Enum.Parse
(
typeof(SupportedType)
,
UnderlyingType.Name
)
)
{
case SupportedType.SByte :
{
Add = new Delegate ( Int8Add ) ;
Subtract = new Delegate ( Int8Subtract ) ;
Multiply = new Delegate ( Int8Multiply ) ;
Divide = new Delegate ( Int8Divide ) ;
Power = new Delegate ( Int8Power ) ;
And = new Delegate ( Int8And ) ;
Or = new Delegate ( Int8Or ) ;
Xor = new Delegate ( Int8Xor ) ;
break ;
}
// Case blocks for the other supported types snipped
}
return ;
}
Equality Operations
Because we constrained the generic parameter to System.IComparable
, we can use the CompareTo
method to implement the equality operations.
These methods differ only by which operator they use.
public static bool
Equal
(
T Op1
,
T Op2
)
{
return ( Op1.CompareTo ( Op2 ) == 0 ) ;
}
Mathematical and Bitwise Operations
These methods differ by what intermediate type and which operator they use.
(The Power methods use System.Math.Pow
.)
private static T
Int8Add
(
T Op1
,
T Op2
)
{
return ( (T) System.Convert.ChangeType
(
(System.SByte) System.Convert.ChangeType ( Op1 , UnderlyingType )
+
(System.SByte) System.Convert.ChangeType ( Op2 , UnderlyingType )
,
UnderlyingType
) ) ;
}
Using the Code
EnumOperators
is most useful in a generic class where an enumeration is provided as a generic parameter.
T T3 EnumOperators<T>.Or ( T1 , T2 ) ;
You may also use it when not in that situation, in which case the only real benefit is the ease of performing mathematical operations on enum
values.
enum MyEnum { Value1 , Value2 , Value3 } ;
MyEnum e = EnumOperators<MyEnum>.Add ( MyEnum.Value1 , MyEnum.Value2 ) ;
History
- 2008-05-22: First submitted