1

I would like to cast an int value as nullable enum used a generic function. I thought it would be easy, especially with all the with all the SO about the enum / int casting. The closest question I've found is this one, but unfortunately, it doesn't handle the nullable enums. This question solve the Nullable enums casting question but not with the generic.

Here's a simplified example of what I'm trying to do:

public enum SouthParkCharacters
{
    Stan = 0,
    Kyle = 1,
    Eric = 2,
    Kenny = 3
}

public static T CastNullableEnum<T>(int value)
{
    return (T)(object)((int?)value).Value;
}


public static T SomeFunction<T>(this int anyEnumValue)
{
        SouthParkCharacters? spc = SouthParkCharacters.Kenny;

        spc = CastNullableEnum<SouthParkCharacters>(3); //I am working!
        spc = CastNullableEnum<SouthParkCharacters?>(3); //Raise error Specified cast is not valid.

        //This function will be public so I won't have control over the T
        T finalValue = CastNullableEnum<T>(anyEnumValue); //Raise error Specified cast is not valid.

        return finalValue;

}

As you can see in last function, a cast for a non-nullable enum is working. The problem is that I want to be able to call the CastNullableEnum from an other generic function, which could use a nullable enum as a generic type.

I know you can pass a generic type as a nullable generic CastNullableEnum<T?> but can we do the opposite (i.e. pass the nullable underlying type to generic function)?

If this can't be done, then I guess the solution would be to find the right way to cast an enum value in the CastNullableEnum function.

Community
  • 1
  • 1
The_Black_Smurf
  • 5,178
  • 14
  • 52
  • 78
  • I think C# support for generic enums is a bit weak. I really wish you could say "where T : enum", but "where T : struct" is the closest you can get. That said, why do you want to be able to have the same method return both a bare enum and a Nullable? It seems better to have your function allways return Nullable and force your caller to check if the result is valid. And I'm assuming you want to return null if the enum doesn't actually have a member with the integer value. Or maybe I'm missing something? – Mr. Putty Aug 14 '14 at 02:33
  • 1
    SouthParkCharacters? is not a truct, so where T:struct is not applicable here. – Hiệp Lê Aug 14 '14 at 02:43
  • To make a long story short, this function will be used to map data from a dbDataReader to a property of a given object. As I don't have control about the given object nor the Generic type, I would like to handle every casting issue on my side. – The_Black_Smurf Aug 14 '14 at 02:44
  • @The_Black_Smurf Have you considered defining one of your enum values as None, i.e. `None = 0,` rather than trying to use null? Also worth considering is storing your enum values in the database as a string (nvarchar). It increases readability when querying or reporting and is easily handled with the ToString and Enum.TryParse methods. – JamieSee Aug 14 '14 at 03:27
  • @JamieSee, yes, I did consider using a none value in my enum for my needs. The problem is that my library might be used as a third party so I won't be able to control the Generic type provided to my function (nor the enum structure / the way it will be stored in the DB). – The_Black_Smurf Aug 14 '14 at 13:38
  • @The_Black_Smurf I don't know if it fits your case at all, but you might find http://specializedenum.codeplex.com interesting. – JamieSee Aug 15 '14 at 00:20

3 Answers3

7

I probably read the question wrong, buy you could grab the underlying type from the nullable and use Enum.Parse instead of a direct cast.

public T CastNullableEnum<T>(int value) 
{
    var enumType = Nullable.GetUnderlyingType(typeof(T));
    if (Enum.IsDefined(enumType, value)) 
    {
        return (T)(object)Enum.Parse(enumType, value.ToString());
    }
    return default(T);
}

var test1 = CastNullableEnum<SouthParkCharacters?>(3); 
//returns Kenny

var test2 = CastNullableEnum<SouthParkCharacters?>(9); 
// returns null
sa_ddam213
  • 42,848
  • 7
  • 101
  • 110
3

This will work:

public static T IntToEnum<T>(object value)
{
    if (value == null)
        return (T)value;

    else if (typeof(T).IsEnum)
        return (T)value;

    else if (Nullable.GetUnderlyingType(typeof(T))?.IsEnum == true)
        return (T)Enum.ToObject(Nullable.GetUnderlyingType(typeof(T)), (int)value);

    throw new Exception(string.Format("Value cannot be converted from {0} to {1}", value.GetType().Name, typeof(T).Name));
}

can be used for nullable and regular enums

public enum Codes
{
    A = 1,
    B = 2,
    C = 3
}

static void Main(string[] args)
{
    Console.WriteLine(IntToEnum<Codes?>(1));
    Console.WriteLine(IntToEnum<Codes?>(null));
    Console.WriteLine(IntToEnum<Codes>(2));
}
Bigjim
  • 2,145
  • 19
  • 19
1

Interesting question. The problem with this code is to cast integer to nullable enum, which is not possible.

  SouthParkCharacters? enumValue = (SouthParkCharacters?)int.MaxValue;
  if (enumValue.HasValue)
     {
         Console.WriteLine("it still has value");
     }

The above code is running and it results the enumValue still HasValue.

But anyway, if you take a look at the C# code after the compiling, it will be something like this:

public static T CastNullableEnum<T>(int value)
    {
        Type type = typeof(T);
        return (T)((object)value);
    }
    public static T SomeFunction<T>(int anyEnumValue)
    {
        Program.SouthParkCharacters? spc = new Program.SouthParkCharacters?(Program.SouthParkCharacters.Kenny);
        spc = new Program.SouthParkCharacters?(Program.CastNullableEnum<Program.SouthParkCharacters>(new int?(3)));
        spc = Program.CastNullableEnum<Program.SouthParkCharacters?>(new int?(3));
        return Program.CastNullableEnum<T>(new int?(anyEnumValue));
    }

Now, notice the

spc = new Program.SouthParkCharacters?(Program.CastNullableEnum<Program.SouthParkCharacters>(new int?(3))); 

That's where the Unspecified Cast Exception comes from; because the new SouthParkCharacters?(int value) expect an Integer.

Funny thing is that you won't encounter this problem when you run with Immediate. Interesting, huh?

Hiệp Lê
  • 636
  • 4
  • 8
  • Yes, I noticed the difference with immediate window... it took me 15 minutes to understand what was going on with my tests. It's really a weird behavior. – The_Black_Smurf Aug 14 '14 at 03:21