6

Here's a bit of a tricky one. Perhaps someone's C#-fu is superior to mine, as I couldn't find a solution.

I have a method that takes a parameter that holds either an enum or a string indicating the value of an Enum and returns an instance of that enum. It's basically an implementation of Enum.Parse but implemented as a generic method. Why the .NET Framework doesn't have this built in is beyond me.

public static T Parse<T>(object value) where T : struct
{
   if (!typeof (T).IsEnum)
      throw new ArgumentException("T must be an Enum type.");

   if (value == null || value == DBNull.Value)
   {
      throw new ArgumentException("Cannot parse enum, value is null.");
   }

   if (value is String)
   {
      return (T)Enum.Parse(typeof(T), value.ToString());
   }

   return (T)Enum.ToObject(typeof(T), value);
}

Now, I can do something like:

MyEnum foo = Parse<MyEnum>(obj);

And get an instance of MyEnum. If obj is null, I throw an exception.

However, sometimes obj is null and I want to allow that. In this case, I'd like to be able to do:

MyEnum? foo = Parse<MyEnum?>(obj);

However, for the life of me, I can't figure out a way to get that working. First off, even though Nullable<MyEnum> is a struct, it's unable to be used as a type parameter to Parse<T>. I think this has something to do with all the magic the compiler does with Nullable<>, so I won't question it.

It doesn't appear you can overload the method and only differentiate it based on constraints on T. For example, if I do:

public static T Parse<T>(object value) where T : new()
{
  // This should be called if I pass in a Nullable, in theory
}

I'll get the error: Member with the same signature is already declared

So, that leaves me with only one option left: Implement a entirely separate method designed for nullable types:

public static T? ParseNullable<T>(object value) where T : struct
{
   if (!typeof (T).IsEnum)
      throw new ArgumentException("T must be an Enum type.");

   if (value == null || value == DBNull.Value)
      return null;

   if (value is String)
      return Enum.Parse(typeof (T), value.ToString()) as T?;

   return Enum.ToObject(typeof (T), value) as T?;
}

I can now call this with:

MyEnum? foo = ParseNullable<T>(obj);

My Question: Is there a way to combine these two methods into a single method that will do the right thing depending on the type parameter, or create overloads where one overload will be used in the case where the type parameter is Nullable<> and the other overload called when it's not?

Mike Christensen
  • 88,082
  • 50
  • 208
  • 326
  • You won't be able to overload the method with the `Nullable` version because you are not changing the parameters (and therefore not overloading); you're only changing the return type. – pickypg Jan 29 '14 at 02:10
  • @pickypg - Yea, that was kind of my question; I was hoping the compiler could overload methods based on generic type constraints alone.. Maybe C# 6 heh.. – Mike Christensen Jan 29 '14 at 05:31

7 Answers7

3

It requires couple additional type checks within the method and you have to skip generic constraint, but it's definitely possible:

public static T Parse<T>(object value)
{
    var isNullable = typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
    var itemType = isNullable ? typeof(T).GetGenericArguments()[0] : typeof(T);

    if (!itemType.IsEnum)
        throw new ArgumentException("T must be an Enum type or Nullable<> of Enum type.");

    if (value == null || value == DBNull.Value)
    {
        if (isNullable)
            return default(T);  // default(Nullable<>) is null

        throw new ArgumentException("Cannot parse enum, value is null.");
    }

    if (value is String)
    {
        return (T)Enum.Parse(itemType, value.ToString());
    }

    return (T)Enum.ToObject(itemType, value);
}

Sample usage:

var items = new object[] { "A", "B", 0, 10, null, DBNull.Value };

var results = items.Select(x => new { x, e = Parse<Test?>(x) }).ToArray();

foreach (var r in results)
    Console.WriteLine("{0} - {1}", r.x, r.e.ToString());

Prints

A - A
B - B
0 - A
10 - B
 -
 -
MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263
  • Yea, there's a few of the same answers, but I like this one the best. The code is easy to read and there's test results at the bottom. – Mike Christensen Jan 29 '14 at 06:19
  • I ended up just using what I had to begin with; two methods with different names. I really wanted the compiler to be able to differentiate which method to use, though I was hoping to do that with an overloaded method. The problem with the approach above is it's going to dig into the type data using reflection at runtime, which defeats the purpose of a generic method. I'm using this in data binding code that potentially is processing hundreds of thousands of rows, so in my case, perf trumps nifty usage syntax. Thanks!! – Mike Christensen Jan 29 '14 at 06:21
  • 1
    @MikeChristensen You're welcome! And btw. I really like the fact that you've decided to do the right thing and use separate methods :) – MarcinJuraszek Jan 29 '14 at 06:28
2

Why not just remove the constraint on T, and do something like this:

    public static T Parse<T>(Object value)
    {
        Boolean isNullable = typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
        if (!isNullable && !typeof(T).IsEnum)
        {
            throw new ArgumentException();
        }

        if (value == null || value == DBNull.Value)
        {
            throw new ArgumentException();
        }

        if (!(value is String))
        {
            return (T) Enum.ToObject(typeof (T), value);
        }

        if (!isNullable)
        {
            return (T) Enum.Parse(typeof (T), value.ToString());
        }

        Type underlyingType = Nullable.GetUnderlyingType(typeof(T));
        try
        {
            return (T)Enum.Parse(underlyingType, value.ToString());
        }
        catch (ArgumentException)
        {
            return default(T);
        }
    }

That should work, if not, let me know.

David Venegoni
  • 508
  • 3
  • 13
1

Create a method like TryParse and handle the return value == false case to do what you want with a null value. You can then implement another method to wrap that call and return null when the return value was false. (Also, be sure to use Enum.IsDefined as any value of the enum's type is assignable to an enum even if it's not defined by the enum)

public static bool TryParseEnum<T>( object value, out T result ) where T : struct
{
    if( !typeof( T ).IsEnum )
        throw new ArgumentException( "T must be an Enum type." );

    if( value == null || value == DBNull.Value )
    {
        result = default( T );

        return false;
    }

    if( value is String )
    {
        return Enum.TryParse<T>( ( string )value, out result );
    }

    result = ( T )Enum.ToObject( typeof( T ), value );

    return Enum.IsDefined( typeof( T ), result );
}

public static Nullable<T> ParseEnum<T>( this object value ) where T: struct
{
    T retVal;

    if( !TryParseEnum( value, out retVal ) )
    {
        return null;
    }

    return new Nullable<T>( retVal );
}

Usage:

EnumXyz? nullableEnumValue = ParseEnum<EnumXyz>( someObject );
Moho
  • 15,457
  • 1
  • 30
  • 31
1

I'm going to offer another method... return the default value. Its a good idea to give enums a default value that represents nothing anyway (if you forget to initialize it, etc)... i.e:

enum MyEnum {
    Nothing = 0,
    MeaningfulValue1,
    MeaningfulValue2
    // etc..
}

Then your method just becomes:

if (value == null || value == DBNull.Value)
    return default(T);

..and the callsite:

var val = Parse<MyEnum>(obj);

if (val == MyEnum.Nothing)
    // it was null.
Simon Whitehead
  • 63,300
  • 9
  • 114
  • 138
  • 2
    Then you never know whether or not the string contained the default value. Also, you can't *not* give an `enum` a default value in .NET, you can only choose not to give any label for it's default value. – Jon Hanna Jan 29 '14 at 02:20
  • default value of an enum is the default value of its underlying type, which would be 0 – Moho Jan 29 '14 at 02:25
  • @JonHanna Fairly easy to add that in. Also, not giving a label makes it hard to nicely check the value once its been assigned.. which is exactly my point in explicitly giving 0 a label so that it can be used as a fallback. – Simon Whitehead Jan 29 '14 at 02:27
  • I think this makes a lot of sense in code that actually has useful defaults; just because there is a default value does not mean that is a sensible one, and generic code like this is not always usable because of that. – pickypg Jan 29 '14 at 02:30
  • My point is that Mike should give the `enum` a default value that has meaning... a meaning of "Nothing". Thereby making generic code like this usable. – Simon Whitehead Jan 29 '14 at 02:31
  • Giving it a label makes it easier to check the value; because at least then it's much easier to distinguish the default-on-error from the default-because-that-is-the-actual-result. It rarely comes up though. This would be mostly useless. – Jon Hanna Jan 29 '14 at 02:36
  • I do generally agree with the idea of giving `enums` a *Nothing* or *Unknown* value. In my case, this won't work. This is for database mapping code where the value of *null* actually has meaning. I need to be able to tell the difference between a database null and a valid enum value. – Mike Christensen Jan 29 '14 at 05:35
1

Since you're not actually overloading by changing the return type, the answer is that you cannot do what you want.

I would add an overload that takes in a separate parameter to determine the null-ability of the parameter.

public static T Parse<T>(object value) where T : struct
{
    return (T)Parse<T>(value, false);
}

public static T? Parse<T>(object value, bool nullable) where T : struct
{
    T? enumValue = null;

    if ( ! typeof(T).IsEnum)
    {
        throw new ArgumentException("T must be an Enum type.");
    }
    else if (value == null || value == DBNull.Value)
    {
        // this is the key difference
        if ( ! nullable)
        {
            throw new ArgumentException("Cannot parse enum, value is null.");
        }
    }
    else if (value is string)
    {
        enumValue = (T)Enum.Parse(typeof(T), value.ToString());
    }
    else
    {
        enumValue = (T)Enum.ToObject(typeof(T), value);
    }

   return enumValue;
}

Usage:

MyEnum value1 = Parse<MyEnum>("A");
// returns null
MyEnum? value2 = Parse<MyEnum>(null, true);
// throws exception
MyEnum? value2 = Parse<MyEnum>(null, false);
pickypg
  • 22,034
  • 5
  • 72
  • 84
1

I believe that the short answer to your question is, "No." In the example you provided at the beginning of your question, you were looking to return two different return types, T and T?. That in itself would require methods with different names.

Here's a link to another question with a great answer on nullables in generic types that might help to clarify the problem for you.

Community
  • 1
  • 1
Scott Corbett
  • 380
  • 1
  • 7
1

If you really want to use one method, then how about this? The downside is you have to remove the where T : struct constraint. If you want to keep the constraint, then separating it into two methods is the only way.

    public static T Parse<T>(object value)
    {
        Type underlyingType = Nullable.GetUnderlyingType(typeof(T));
        bool isNullable = underlyingType != null;

        if (!typeof(T).IsEnum && !isNullable)
            throw new ArgumentException("T must be an Enum type.");

        if (value == null || value == DBNull.Value)
        {
            if (isNullable)
                return default(T);

            throw new ArgumentNullException("value");
        }

        if (value is String)
            return (T)Enum.Parse(underlyingType ?? typeof(T), value.ToString());

        if (!value.GetType().IsValueType)
            throw new ArgumentException("value must be a primitive type", "value");

        return (T)Enum.ToObject(underlyingType ?? typeof(T), value);
    }
Hack
  • 1,408
  • 10
  • 13
  • It still throws an exception for `null` even when `T` is `Nullable<>`. – MarcinJuraszek Jan 29 '14 at 03:44
  • Yea, I was thinking of going down this path, however it seemed to really miss the whole point of generic methods if you're gonna end up resolving all the type information at runtime anyway. This approach might be my best bet though. – Mike Christensen Jan 29 '14 at 05:32