65.9K
CodeProject is changing. Read more.
Home

EnumOperators

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (10 votes)

May 22, 2008

CPOL

2 min read

viewsIcon

34622

downloadIcon

146

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