141

Similar to Cast int to enum in C# but my enum is a Generic Type parameter. What is the best way to handle this?

Example:

private T ConvertEnum<T>(int i) where T : struct, IConvertible
{
    return (T)i;
}

Generates compiler error Cannot convert type 'int' to 'T'

Full code is as follows, where value can contain the int, or null.

private int? TryParseInt(string value)
{
    var i = 0;
    if (!int.TryParse(value, out i))
    {
        return null;
    }
    return i;
}

private T? TryParseEnum<T>(string value) where T : struct, IConvertible
{
    var i = TryParseInt(value);
    if (!i.HasValue)
    {
        return null;
    }

    return (T)i.Value;
}
3

10 Answers 10

179

The simplest way I have found is to force the compiler's hand by adding a cast to object.

return (T)(object)i.Value;
Sign up to request clarification or add additional context in comments.

6 Comments

We are casting enum to int, not the contrary as in the So question you link. Also, that question has no solution.
You could also just allocate a static array with the enum values, and then just pass in the index to retrieve the correct enum. This saves having to do any kind of casting. Example(Only line 11,14, and 34 are relevant to this concept): pastebin.com/iPEzttM4
I feel kind of evil doing this, but hey, it works
This will throw an exception if the enum's underlying type is not int (i.e. byte or long enums, even uint).
|
27

You should be able to use Enum.Parse for this:

return (T)Enum.Parse(typeof(T), i.Value.ToString(), true);

This article talks about parsing generic enums for extenstion methods:

2 Comments

@Guvante: I think I converted the value to a string in my example. Do you foresee this causing an issue?
FYI: ALso consider Enum.IsDefined after the parse.
20

Here's a very fast solution that abuses the fact that the runtime creates multiple instances of static generic classes. Unleash your inner optimization demons!

This really shines when you're reading Enums from a stream in a generic fashion. Combine with an outer class that also caches the enum's underlying type and a BitConverter to unleash the awesome.

void Main() 
{
    Console.WriteLine("Cast (reference): {0}", (TestEnum)5);
    Console.WriteLine("EnumConverter: {0}", EnumConverter<TestEnum>.Convert(5));
    Console.WriteLine("Enum.ToObject: {0}", Enum.ToObject(typeof(TestEnum), 5));

    int iterations = 1000 * 1000 * 100;
    Measure(iterations, "Cast (reference)", () => { var t = (TestEnum)5; });
    Measure(iterations, "EnumConverter", () => EnumConverter<TestEnum>.Convert(5));
    Measure(iterations, "Enum.ToObject", () => Enum.ToObject(typeof(TestEnum), 5));
}

static class EnumConverter<TEnum> where TEnum : struct, IConvertible
{
    public static readonly Func<long, TEnum> Convert = GenerateConverter();

    static Func<long, TEnum> GenerateConverter()
    {
        var parameter = Expression.Parameter(typeof(long));
        var dynamicMethod = Expression.Lambda<Func<long, TEnum>>(
            Expression.Convert(parameter, typeof(TEnum)),
            parameter);
        return dynamicMethod.Compile();
    }
}

enum TestEnum 
{
    Value = 5
}

static void Measure(int repetitions, string what, Action action)
{
    action();

    var total = Stopwatch.StartNew();
    for (int i = 0; i < repetitions; i++)
    {
        action();
    }
    Console.WriteLine("{0}: {1}", what, total.Elapsed);
}

Results on Core i7-3740QM with optimizations enabled:

Cast (reference): Value
EnumConverter: Value
Enum.ToObject: Value
Cast (reference): 00:00:00.3175615
EnumConverter: 00:00:00.4335949
Enum.ToObject: 00:00:14.3396366

2 Comments

This is really nice, thanks. You might like to use Expression.ConvertChecked instead though, so that numeric overflow of the enum type's range results in an OverflowException.
Your mileage might vary, I ran the code on try.dot.net (blazor) and there the EnumConverter<T> is much slower than the alternatives. Casting to object first was about 6 times slower than a direct cast, but still far better than the other options.
20

In .NET core it is now possible to use System.Runtime.CompilerServices.Unsafe code like this:

return Unsafe.As<int, TEnum>(ref int32);

Comments

8

Alternatively, if you can get a enum not as a generic type, but as Type, then simply use

Enum.ToObject

https://msdn.microsoft.com/en-us/library/system.enum.toobject(v=vs.110).aspx

Comments

4
public static class Extensions
    {
        public static T ToEnum<T>(this int param)
        {
            var info = typeof(T);
            if (info.IsEnum)
            {
                T result = (T)Enum.Parse(typeof(T), param.ToString(), true);
                return result;
            }

            return default(T);
        }
    }

1 Comment

You can add some constraints on there. Makes the if (info.IsEnum) check unnecessary. public static T ToEnum<T>(this int param) where T : struct, Enum
3

Since .NET 8, you can use the Unsafe.BitCast<TFrom,TTo>(TFrom) method in order to cast an int to a generic enum.

using System;
using System.Runtime.CompilerServices;

namespace StackOverflow.SampleCode;

public static class EnumExtensions
{
    public static TEnum ConvertEnum<TEnum>(this int value) where TEnum : struct, Enum
    {
        return Unsafe.BitCast<int, TEnum>(value);
    }
}

For reference, this was discussed in [API Proposal]: Unsafe.BitCast for efficient type reinterpretation (#81334) and implemented in Adding System.Runtime.CompilerServices.Unsafe.BitCast (#82917).

Comments

1

This unsafe conversion can be used in .net framework since c# 7.3, supposing the underlying type is int.

unsafe static TEnum Int2EnumUnsafe<TEnum>(int i) where TEnum : unmanaged, Enum
        => *(TEnum*)&i;

Comments

0

((T[])Enum.GetValues(typeof(T))) can be used to build a dictionary / lookup table from int to the Enum type Overall I prefer Ramon's cast using "Unsafe.As" because enums are such a thin veneer over integers that it doesnt seem worth building castles around the pretense (not that the thinness is a bad thing). Notice the Enum type contsraint from c# 7.3. (Basic thing is that we can cast the array of T under the generic enum constraint)

(this would be a comment if I had rep)

    public static TEnum IntToEnum<TEnum>(int i)
    where TEnum : Enum
    {
        Array array = Enum.GetValues(typeof(TEnum));
        int[] intValues = (int[])array;
        TEnum[] enumValues = (TEnum[])array;
        var b = intValues.Zip(enumValues);
        //Consider saving the dictionary to avoid recreating each time
        var c = b.ToDictionary<(int n, TEnum e), int, TEnum>(p => p.n, p => p.e);
        return c[i];//KeyNotFoundException possible here
    }

Should work after the horrible error pointed out by @trinalbadger587 (thanks.. https://dotnetfiddle.net/1oYWjD )

4 Comments

And of course the same thing can be done without using linq, replacing the .Zip, .ToDictionary lines with Dictionary<int, TEnum> c = new Dictionary<int, TEnum>(array.Length); for (int j = 0; j < array.Length; j++) c.Add(intValues[j], enumValues[j]);
Also same Array cast can be used to set a generic enum to an arbitrary integer value; TEnum enumValue = ((TEnum[])(Array)(new int[] { -1 }))[0];
You can see this answer on .NET fiddle: dotnetfiddle.net/Nrc2oL
Thinks about performance bro
0

This is a new answer because it is a different take. Ancient question, but I was doing this yesterday... so

Similar to @Ramon-de-Klein and using the dotnet-fiddle example from @trinalbadger587

Rather terse and opaque, but sometimes thats ok. Note that it needs the right underlying value type if the enum is stored in a byte or 16 bit ushort

        //Int to Enum using the hot span newness - but without unsafe{}
        Span<int> XS = stackalloc int[] { 100 };
        Console.WriteLine(MemoryMarshal.Cast<int, Bla>(XS)[0]);

        //Int to Enum using good old arrays
        Console.WriteLine(((Bla[])(Array)(new int[] { 100 }))[0]);

        //Enum to Int
        Console.WriteLine(((int[])(Array)(new Bla[] { Bla.B }))[0]);


enum Bla
{
    A = 0,
    B = 100
}

1 Comment

Where generics ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.