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?
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
-
-
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.
I'm going to offer another method... return the default value. Its a good idea to give enum
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With