9/14/07

Making C# enums more usable - the Parse() method

I'll try and accumulate some feedback and thoughts about using enums in C#. There are several issues I see with the current (C# 2.0) enum API:

  • Methods like Parse() are not strongly typed

  • Working with flags and bits is a little cumbersome

  • There is no generic constraint where T: Enum

Today I'll start with the first post about the Parse method. Here's how to use it:

MyEnum enumValue = (MyEnum)Enum.Parse(typeof(MyEnum), stringValue);

This is somewhat ugly. Christopher Bennage proposes a solution with generics which I like (see his post). Also, there are a lot of other links about the lack of generic methods on the Enum class (see posts by Scott Watermasysk, CyrusN, Dustin Campbell etc.)

Still, a generic solution is not perfect from the readability point of view. What I mean is, wouldn't it be nice if the C# compiler could generate a strongly typed Parse method directly on the MyEnum type, so that it could read:

MyEnum enumValue = MyEnum.Parse(stringValue);

See also a nice suggestion at MS Connect: https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=96897

But the problem is, most probably you'd have to change the CLR to achieve this behavior, not to mention all the tools that think that enum types cannot have members. I'm not exactly sure, if the C# compiler can generate custom methods on enum types and whether you'd have to change the CLR for it. The reason is that enums (inheriting from the special class System.Enum) are treated a little bit different than other classes. I used ILDASM.exe to view the IL for the following code:

namespace TestEnum
{
public enum MyEnumEnum
{
A,
B
}

public sealed class MyEnumClass
{
public static MyEnumClass A;
public static MyEnumClass B;
public int value__;
}
}

In ILDASM, it looks like this:


See, an enum looks internally almost like a class - maybe it is not difficult to add methods to it?

Anyway, you can vote for such features to be implemented:
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=98356
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=293587

However, we can use extension methods in C# 3.0! Like this:

public static T ParseAsEnum<T>(this string value)
// where T : enum
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException
("Can't parse an empty string");
}

Type enumType = typeof(T);
if (!enumType.IsEnum)
{
throw new InvalidOperationException
("Here's why you need enum constraints!!!");
}

// warning, can throw
return (T) Enum.Parse(enumType, value);
}

It could be used like:

DriveType disk = "Network".ParseAsEnum<DriveType>();


Update: Thomas Watson also proposed adding an extension method directly to the Enum class in the comments here. Cool!

4 comments:

Anonymous said...

And then there’s the Java way:

DayOfWeek dow = DayOfWeek.valueOf("Monday");

Full working example:

enum DayOfWeek {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};

public class Ex
{
     public static void main (String [] args)
     {
        DayOfWeek dow =         DayOfWeek.valueOf("Monday");

        System.out.println(dow);
     }
}

Kirill Osenkov said...

Yeah, that's a good API. Great that Java has it.

Anonymous said...

Hello, i'd like to has nullable return value and no exceptions to be thrown, in the case of incorrect value to be parsed return null, is that possible?
(currently i cannot build)

public static Nullable(T) TryParseEnum(T)(string val)
{
if (!string.IsNullOrEmpty(val))
{
Type et = typeof(T);
foreach (string v in Enum.GetValues(et))
{
if (v == val)
return (T)Enum.Parse(et, val);
}
}
return null;
}

Kirill Osenkov said...

No, this is not possible, because you'd have to specify that T is a non-nullable value type, and where T : enum generic constraints are not supported in the language.

But here's what you can do:

using System;

class Program
{
static void Main(string[] args)
{
DayOfWeek d;
if (TryParseEnum<DayOfWeek>("Monday", out d))
{
Console.WriteLine(d);
}
}

public static bool TryParseEnum<T>(string val, out T result)
{
result = default(T);
if (!string.IsNullOrEmpty(val))
{
Type et = typeof(T);
if (!et.IsEnum)
{
return false;
}
try
{
result = (T)Enum.Parse(et, val);
return true;
}
catch (Exception)
{
}
}
return false;
}

}