简体   繁体   中英

General handling of System.Enum values regardless of underlying type in C#

I implemented a method that takes two Enum values as parameters:

void HandleEnumValue( System.Enum v, System.Enum bits )

The actual enum-type is not necessarily the same for the two parameters..

In this method I assumed that the underlying type of enum is int. Now for the first time this method is called with enum values that have long as the underlying type, and the method now throws an exception.

I like to enhance the method so that it can take any enum-value , regardless of the underlying type. I have an implementation for that, but I don't like it very much.

I wonder if there is a more general/maintainable solution?

Alas, I cannot use a generic type parameter, because this would introduce a breaking change in the library, and the zillions of calls to my method would have to be replaced.

Here's the code, and my not-so-good-solution:

using System;

[Flags()] public enum ByteEnum : byte { One = 1, SomeByte  = 0x42 } 
[Flags()] public enum ShortEnum: short{ One = 1, SomeShort = 0x4223 } 
[Flags()] public enum IntEnum  : int  { One = 1, SomeInt   = 0x42230815 } 
[Flags()] public enum LongEnum : long { One = 1, SomeLong  = 0x4223081547112012L } 

public class Program 
{
    public static void Main()
    {
        HandleEnumValue( ByteEnum.SomeByte,   ByteEnum.One  ) ;
        HandleEnumValue( ShortEnum.SomeShort, ShortEnum.One ) ;
        HandleEnumValue( IntEnum.SomeInt,     IntEnum.One   ) ;
        HandleEnumValue( LongEnum.SomeLong,   LongEnum.One  ) ;

        // will throw InvalidCastException: HandleEnumValueOriginal( ByteEnum.SomeByte,   ByteEnum.One  ) ;
        // will throw InvalidCastException: HandleEnumValueOriginal( ShortEnum.SomeShort, ShortEnum.One ) ;
        HandleEnumValueOriginal( IntEnum.SomeInt,     IntEnum.One   ) ;
        // will throw InvalidCastException: HandleEnumValueOriginal( LongEnum.SomeLong,   LongEnum.One  ) ;
    }

    // new implementation, that I dislike.
    static void HandleEnumValue( System.Enum v, System.Enum bits )
    {
        // assert underlying types of v and bits are equal
        if ( v.GetTypeCode() != bits.GetTypeCode() ) throw new Exception( "enum type code mismatch" ) ;

        bool hasBitsSet = false ;   // won't compile: bool hasBitsSet = ( ( v & bits ) == bits ) ;
        long lvalue     = 0L ;      // will throw   : lvalue = (long)(object)v

        switch ( v.GetTypeCode() )
        {
            case TypeCode.Byte : 
                hasBitsSet = ( ( (byte)(object)v &  (byte)(object)bits ) ==  (byte)(object)bits ) ;
                lvalue = (long)(byte)(object)v ;
                break ;
            case TypeCode.Int16 : 
                hasBitsSet = ( ( (short)(object)v &  (short)(object)bits ) ==  (short)(object)bits ) ;
                lvalue = (long)(short)(object)v ;
                break ;
            case TypeCode.Int32 : 
                hasBitsSet = ( ( (int)(object)v &  (int)(object)bits ) ==  (int)(object)bits ) ;
                lvalue = (long)(int)(object)v ;
                break ;
            case TypeCode.Int64 : 
                hasBitsSet = ( ( (long)(object)v &  (long)(object)bits ) ==  (long)(object)bits ) ;
                lvalue = (long)(object)v ;
                break ;
        }

        Console.WriteLine( "(new) enum value   = " + v.ToString() ) ;
        Console.WriteLine( "(new) number value = " + lvalue.ToString() ) ;
        Console.WriteLine( "(new) has bits set = " + hasBitsSet.ToString() ) ;

        // further processing ...
    }

    // original implementation, that doesn't work anymore
    static void HandleEnumValueOriginal( System.Enum v, System.Enum bits )
    {
        int  lvalue     = (int)(object)v ;
        bool hasBitsSet = ( ( (int)(object)v & (int)(object)bits ) == (int)(object)bits ) ;
        Console.WriteLine( "(original) enum value   = " + v.ToString() ) ;
        Console.WriteLine( "(original) number value = " + lvalue.ToString() ) ;
        Console.WriteLine( "(original) has bits set = " + hasBitsSet.ToString() ) ;
        // further processing ...
    }

}

In .NET 4, there is an added method that does almost what you want: Enum.HasFlag .

Unfortunately, this method throws if the actual enum types are different. Its implementation is quite simple to replicate without enum type checking or if you're using an older version of the framework:

static void HandleEnumValue(Enum v, Enum bits) {
    ulong enumValue = ToUInt64(v);
    ulong bitsValue = ToUInt64(bits);
    bool hasBitsSet = (enumValue & bitsValue) == bitsValue;
    Console.WriteLine("enum value   = " + v);
    Console.WriteLine("number value = " + bits);
    Console.WriteLine("has bits set = " + hasBitsSet);
}

internal static ulong ToUInt64(Enum value) {
    switch (Convert.GetTypeCode(value)) {
        case TypeCode.SByte:
        case TypeCode.Int16:
        case TypeCode.Int32:
        case TypeCode.Int64:
            return (ulong) Convert.ToInt64(value);
        case TypeCode.Byte:
        case TypeCode.UInt16:
        case TypeCode.UInt32:
        case TypeCode.UInt64:
            return Convert.ToUInt64(value);
        default:
            throw new ArgumentOutOfRangeException("value");
    }
}

This method will handle any type of enum and any underlying type, even if they're different.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM