83

I have a small method that looks like this:

public void SetOptions<T>() where T : Enum
{
    int i = 0;
    foreach (T obj in Enum.GetValues(typeof(T)))
    {
        if (i == 0)
            DefaultOption = new ListItem(obj.Description(), obj.ToString());
        i++;
        DropDownList.Items.Add(new ListItem(obj.Description(), obj.ToString()));
    }
}

Basically, I populate a dropdown list from an enum. Description() is actually an extension method for enums, so T is definitely an enum.

However, I want to cast obj just as you would any enum to its index like this (int)obj, but I get an error saying I can't convert T to int. Is there a way to do this?

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
dotnetnoob
  • 10,013
  • 19
  • 53
  • 98
  • 7
    `enum` cannot be part of the generic constraint. – Dustin Kingen Jun 06 '13 at 11:10
  • What is it exactly you are trying to accomplish? Populating the values of a drop down with the values from an enumerated type? – Candide Jun 06 '13 at 11:30
  • I have a number of child usercontrols that all need to populate a base dropdownlist. The dropdownlist is on a base usercontrol. Previously the list was built in each of the child controls and then passed to the base usercontrol so no generics required. However looking to just pass the enum type and let the base control do the rest. – dotnetnoob Jun 06 '13 at 11:36
  • 10
    `enum` CAN be part of a generic type constraint as of May 7th, 2018 with the release of Visual Studio v15.7 with C# 7.3. https://stackoverflow.com/a/28527552/88409 However, this cast may still not be possible, haha. – Triynko May 15 '18 at 22:05

13 Answers13

70

You could also cast your value to object first and then to int.

C# 7.3 and above

With the Enum generic constraint.

public static int EnumToInt<TValue>(this TValue value) where TValue : Enum
    => (int)(object)value;

Below C# 7.3

Without the Enum generic constraint.

public static int EnumToInt<TValue>(this TValue value)  where TValue : struct, IConvertible
{
    if(!typeof(TValue).IsEnum)
    {
        throw new ArgumentException(nameof(value));
    }

    return (int)(object)value;
}

If your enum inherits from other types for example from byte the cast to int will throw an InvalidCastException.

You could either check if the base type of the enum is an integer.

public static int EnumToInt<TValue>(this TValue value) where TValue : Enum
{
    if (!typeof(int).IsAssignableFrom(Enum.GetUnderlyingType(typeof(TValue))))
        throw new ArgumentException(nameof(TValue));

    return (int)(object)value;
}

Or you if you use Convert.ToInt32 it will use the IConvertible interface of int32 to convert the incompatible types.

public static int EnumToInt<TValue>(this TValue value) where TValue : Enum
    => Convert.ToInt32(value);

Just be aware the converting uint to int and signed/unsigned pairs can cause unintended behavior. (Boxing to IConvertible and the converting is less performant than just unboxing.)


I recommend creating a method for each enum base type to ensure the correct result is returned.

GregC
  • 7,635
  • 2
  • 52
  • 64
NtFreX
  • 8,977
  • 2
  • 37
  • 61
  • Using the C# 7.3 approach I sometimes get `InvalidCastException`. VS's Immediate window says "Cannot convert type 'TValue' to 'int'" which is weird. The debugger window shows that the `TValue value` value in-memory is typed as the enum. – Dai Sep 29 '18 at 04:14
  • @Dai I've updated my answer to reflect your problems. – NtFreX Oct 01 '18 at 11:18
  • 1
    You can use `(TEnum)(ValueType)value` to avoid casting to `object`. – Ryan Jun 22 '21 at 06:45
  • 1
    @Ryan `ValueType` is also class, though, so it wouldn't avoid boxing anyway. – Oscar Abraham Jul 05 '21 at 00:52
57

try this,

public void SetOptions<T>()
{
    Type genericType = typeof(T);
    if (genericType.IsEnum)
    {
        foreach (T obj in Enum.GetValues(genericType))
        {
            Enum test = Enum.Parse(typeof(T), obj.ToString()) as Enum;
            int x = Convert.ToInt32(test); // x is the integer value of enum
                        ..........
                        ..........
        }
    }
}
petchirajan
  • 3,922
  • 1
  • 17
  • 20
7

If you are targeting .NET Core, you can utilize Unsafe.As<TFrom, TTo> in the System.Runtime.CompilerServices namespace, as explained on MSDN. The advantage here is there will be no boxing done, which is the only real performance cost in the other answers here.

private static int EnumToInt<TEnum>(TEnum enumValue) where TEnum : Enum
{
    return Unsafe.As<TEnum, int>(enumValue);
}

Note that this approach suffers from the safe issue as other existing answers do: there is not a guarantee that the given enum is a compatible int type, which is likely the same reason this functionality is not baked-in. If you are using this approach internally where you can be sure that any enum passed to it is a compatible type, then this is likely the most efficient approach.

Here is a link to an issue on dotnet's GitHub page where where this issue was raised, and some of the developers elaborated a bit on this approach if you would like to learn more.

ForeverZer0
  • 2,191
  • 1
  • 20
  • 28
7

Here's my solution for C# 7.3 and up. Not an exact match to OP's question, but probably useful for people finding this from Google. The main advantage over the other answers is it returns a ulong, which means any of the permissible enum types will fit in it. I also made a comparison of the machine code for this and a few of the other answers. Yes, I was bored and in the mood for a little premature optimization.

private static unsafe ulong EnumAsUInt64<T>(T item) where T : unmanaged, Enum
{
    ulong x;
    if (sizeof(T) == 1)
        x = *(byte*)(&item);
    else if (sizeof(T) == 2)
        x = *(ushort*)(&item);
    else if (sizeof(T) == 4)
        x = *(uint*)(&item);
    else if (sizeof(T) == 8)
        x = *(ulong*)(&item);
    else
        throw new ArgumentException("Argument is not a usual enum type; it is not 1, 2, 4, or 8 bytes in length.");
    return x;
}
scott
  • 1,025
  • 1
  • 10
  • 19
5

This one is working with any underlying type

Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()))

For example when you want add a value to SqlCommand, which converts enums to 0 and you have to explicitly cast it to matching type. But we can write following extension:

public static void AddEnum(this SqlParameterCollection parameters, string parameterName, Enum value)
{
    parameters.AddWithValue(parameterName, Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType())));
}

Which is doing everything for us.

Alex Zhukovskiy
  • 8,821
  • 5
  • 61
  • 138
4

Can you abuse GetHashCode for this?

public enum MyEnum
{
  Foo = 100,
  Bar = 200,
  Fizz = 0
}

static void Main(string[] args)
{
  var i1 = MyEnum.Foo.GetHashCode();  // i1 = 100
  var i2 = MyEnum.Bar.GetHashCode();  // i2 = 200
  var i3 = MyEnum.Fizz.GetHashCode(); // i3 = 0
}

Please note: "GetHashCode() is by design useful for only one thing: putting an object in a hash table. Hence the name." - E. Lippert

jan
  • 1,571
  • 2
  • 19
  • 33
  • 1
    There is no guarantee that `GetHashCode` always returns the underlying integer or that its implementation will remain the same between .NET versions. That said, I don't see why they'd ever change it. – Edward Brey Feb 13 '16 at 15:23
  • Is `GetHashCode()` secure to use as a dictionary key in stead of an int cast for enums? – noio Feb 18 '16 at 17:46
  • 1
    @noio `GetHashCode()` is "by design useful for only one thing: putting an object in a hash table" ([said E. Lippert himself](https://blogs.msdn.microsoft.com/ericlippert/2011/02/28/guidelines-and-rules-for-gethashcode/)) (which invalidates my own answer btw) I guess it is safe, but I wouldn't use it for creating keys for use in a dictionary: why not just use the enums as-is? – jan Feb 19 '16 at 14:30
  • There is an issue with Boxing when the dict key is a generic type (`Dictionary`) constrained by `class MyClass where TEnum: struct, IComparable` in the generic class definition (which I know to be an Enum, but not _which_ enum). So instead I created a `Dictionary` and do a manual `myDictionary[myEnumValue.GetHashCode()]` everywhere. – noio Feb 20 '16 at 15:32
  • @noio If you don't know which enum, then you also can't know that the enum values won't collide since different enums can use the same numeric value. I suggest using the closed type (i.e. `MyClass`) as the dictionary key -or- if you define all the enums and give each a specific range, you may consider merging them into one. – Şafak Gür Dec 04 '17 at 12:34
  • That is a great way to become allocation free. The Convert.ToInt32(object obj) answer causes boxing which is no different than (int) (object) T obj – Alois Kraus Jan 03 '19 at 15:17
4

Here's a simpler way.

Since Enum implements IConvertible we can use ToInt32(..).

int? value = (enumCandidate as IConvertible)?.ToInt32(CultureInfo.InvariantCulture.Numberformat);

Or if you'd like a general purpose method for generic enums:

public static int GetEnumValue<T>(T inputEnum) where T: struct, IConvertible
{
    Type t = typeof(T);
    if (!t.IsEnum)
    {
        throw new ArgumentException("Input type must be an enum.");
    }

    return inputEnum.ToInt32(CultureInfo.InvariantCulture.NumberFormat);

}

Or yet more general:

public static int GetEnumValue(object enumInput)
{
    Type t = enumInput.GetType();
    if (!t.IsEnum)
    {
        throw new ArgumentException("Input type must be an enum.");
    }

    return ((IConvertible)inputEnum).ToInt32(CultureInfo.InvariantCulture.NumberFormat);

}
3

if you restict the generic T to be an Enum using

where T: Enum

you can then use the one-liner below

public static int GetIndexFromEnum<T>(T enumValue) where T : Enum {
    int index = Convert.ToInt32(enumValue);
    return index;
}

This seems like the simplest solution, as long as you can guarantee T is going to be an Enum.

Adam B
  • 3,092
  • 2
  • 20
  • 30
3

Just cast the generic T to object first

T value;
int int_value = (int)(object)value;

That's it.

manCuongCs
  • 31
  • 1
  • This is very similar to @NtFreX‘s answer from two years ago, except they provide more explanation and also address how to handle cases where the code is written for earlier versions of C#. – Jeremy Caney May 20 '20 at 08:38
1

I'm surprised your code works at all. Enum.GetValues returns an array of integers - which are the values you're looking for. And, as others have mentioned, you can't constrain your generics to an enum.

Instead, you should probably call your Description method as a regular static method and not an extension method.

Community
  • 1
  • 1
zmbq
  • 36,789
  • 13
  • 91
  • 160
1

Using LINQ this can be done elegantly:

public static void SetOptions<T>(this DropDownList dropDownList)
{
    if (!typeof(T).IsEnum)
    {
        throw new ArgumentException("Type must be an enum type.");
    }

    dropDownList.Items.AddRange(Enum
        .GetValues(typeof(T))
        .Cast<Enum>()
        .Select(x => new ListItem(x.ToString(), Convert.ToInt32(x).ToString()))
        .ToArray());
}
Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
matk
  • 1,518
  • 1
  • 14
  • 25
0

Try this: (assuming that the TEnum has a numeration from 0 to n)

public void SetOptions<TEnum>() where TEnum : Enum
{
    foreach (TEnum obj in Enum.GetValues(typeof(TEnum)))
    {
        var i = (int)(object)obj;
        if (i == 0) DefaultOption = new ListItem(obj.Description(), obj.ToString());
        DropDownList.Items.Add(new ListItem(obj.Description(), obj.ToString()));
    }
}
Emiliano
  • 77
  • 6
-3

To expand on Jan's answer concerning generics and enum values:

void MyFunc<T>(T value)
{
    Type t = typeof(T);
    if(t.IsEnum)
    {
        int valueAsInt = value.GetHashCode(); // returns the integer value
    }
}
P. Avery
  • 671
  • 1
  • 15
  • 32
  • 2
    Doesn't this assume that `Enum.GetUnderlyingType()` is actually an `int`. If you happen to be using `public enum : long { }` for any reason, you are going to be compacting an 8-byte value into a 4-byte one... – jimbobmcgee Nov 12 '15 at 17:13