3

I know this may sound a bit weird, but I want to cast an object of T? to int?. The reason why I want this is that T is always an enum and I also know this enum will be castable to int. I'm creating a generic wrapper class for enums and found this "solution" to constrain the generic type to be an enum type.

My code currently looks like this:

public class EnumWrapper<T>
    where T : struct, IConvertible
{
    public EnumWrapper()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("T must be an enumerated type");
        }
    }

    public T? Enum { get; set; }

    public int? EnumValue => (int?)Enum;    // this won't compile

    ...
}

I know that casting to object before casting to a value type ((int?)(object)Enum) tricks the compiler, but does this work here too? I suspect the nullable interferes with this.

So my question is: What is the best way to implement this generic behavior?

Community
  • 1
  • 1
Florian Koch
  • 1,372
  • 1
  • 30
  • 49
  • Perhaps `new Nullable((int)(this.Enum as object))`? – haim770 Nov 02 '16 at 08:58
  • yeah this compiles, but does this work correctly with the nullable? Generally, there should be no difference to `(int?)(object)Enum`, or am I wrong? – Florian Koch Nov 02 '16 at 08:59
  • It does seem to work correct with `Nullable` too – haim770 Nov 02 '16 at 09:04
  • Ok, I'll try this out. Still, it doesn't seem to me like the best solution... – Florian Koch Nov 02 '16 at 09:05
  • 2
    A more elegant solution using C# 6: `public int? EnumValue => this.Enum?.ToInt32(CultureInfo.CurrentCulture.NumberFormat);` – haim770 Nov 02 '16 at 09:09
  • This certainly looks nicer! – Florian Koch Nov 02 '16 at 09:11
  • 3
    Just because it's an enum, that doesn't guarantee that it can be cast to an `int`. Notably, enums can be based on `uint`s or `long`/`ulong`. – Damien_The_Unbeliever Nov 02 '16 at 09:11
  • @Damien_The_Unbeliever yeah you are right, but in my case this isn't going to happen - I even use Int16 (short) in the real code because it will never be that high – Florian Koch Nov 02 '16 at 09:13
  • @Damien_The_Unbeliever, True. But since the OP already exposes the `EnumValue` property as `int?`, it will be fine in this case. Otherwise, there is no reason for the wrapper anyway. – haim770 Nov 02 '16 at 09:15
  • The other day I posted a message on CodeProject titled "Fun with Generic Enum Constraint" (see www.codeproject.com/Feature/WeirdAndWonderful.aspx?msg=5071953 ). Well, you were struck by those problems, too. – Bernhard Hiller Nov 02 '16 at 11:49

1 Answers1

2

Since T is IConvertible, you can avoid casting and boxing using the ToInt32() method:

public int? EnumValue => this.Enum?.ToInt32(CultureInfo.CurrentCulture.NumberFormat);

And this.Enum? will make sure to return null in case the actual value is missing.

Also, since we're trying to retain the value-type semantics of the wrapped Enum, you'll want to change EnumWrapper from class to struct and use an extension method to easily wrap the value:

public struct EnumWrapper<T> where T : struct, IConvertible
{
    public T? Enum { get; set; }
    public int? EnumValue => this.Enum?.ToInt32(CultureInfo.CurrentCulture.NumberFormat);
}

public static class EnumWrapperExtension
{
    public static EnumWrapper<T> Wrap<T>(this T data) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException("T must be an enumerated type");

        return new EnumWrapper<T> { Enum = data };
    }
}

Usage:

public enum Color
{
    Red = 1,
    Green = 2,
}

var color = Color.Green;
var colorAsInt = color.Wrap().EnumValue;

See MSDN

haim770
  • 48,394
  • 7
  • 105
  • 133