Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to combine these two methods into one method, or overloaded methods?

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?

like image 540
Mike Christensen Avatar asked Jan 29 '14 01:01

Mike Christensen


3 Answers

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
 -
 -
like image 118
MarcinJuraszek Avatar answered Sep 21 '22 01:09

MarcinJuraszek


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.

like image 26
David Venegoni Avatar answered Sep 18 '22 01:09

David Venegoni


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.
like image 42
Simon Whitehead Avatar answered Sep 19 '22 01:09

Simon Whitehead