Click here to Skip to main content
15,881,866 members
Articles / Multimedia / GDI

Safe Cast string to enum

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
2 Mar 2014CPOL4 min read 11.2K   3   2
Safe cast string to enum

Introduction

In one of my projects, I had to make an interface between database and web service in C# code.

One of the problems I had to face was the need to cast strings to enumerable types, because of the simple fact that database has no idea what is an 'enum'. Yes, the simplest mapping between an enumeration and database type is integer. It is the simplest but in my opinion, not the best. For example, I really do not remember (or do not WANT to remember) what is the meaning of 4 integer value in case of package shipment status.

We can have values like this:

  • 0 - RequestReceived
  • 1 - Accepted
  • 2 - Assembling
  • 3 - Sended
  • 4 - CustomerReceived
  • 5 - Completed

Let's assume that is a valid flow of package delivery flow. For someone that is working on a system that handles monitoring of that kind of mechanism, this could be not an issue, because this flow is easy to recall. Yes. But as the company grows, software grows. And most likely, more enumeration types will be created.

So to have the best of two worlds, in my opinion, it is best to have enumeration in strings and with possibility to cast strings like '0' and 'RequestReceived' to RequestReceived enum value.

A nice feature is to make casting also case insensitive. But this is not necessary.

Aside from interfacing with database, there are other use cases that come to mind:

  1. User interfaces input
  2. Type serialization to JSON and from JSON
  3. XML serialization
  4. Import from various data sources like CSV

Ok. That is for introduction. Let's go to the coding.

First, we have to retrieve values of enum type:

C#
var values = Enum.GetValues(typeof(TEnum));

This is simple. Static method of Enum special class returns all possible values of an enum. With that knowledge, we can just use foreach loop to iterate through collection:

C#
public static EnumTest GetEnumValue(string val)
{
    var values = Enum.GetValues(typeof(EnumTest));
    foreach (EnumTest value in values)
    {
        if (val.Equals(value.ToString()))
        {
            return value;
        }
    }
    return 0;
}

There are a few problems with this method. First, we can use it with only one predefined type of enum. Sadly, this is impossible to create generic enum-only method in C#. But we can do pretty close. Second problem is that we don't need to have default values of enum type with integer equivalent of 0. 0 cannot be in an enum at all!
For the first issue, we can add generic type argument with constraints of interfaces implemented by enum types and with struct special constraint.
For the second issue, we can use default C# keyword. Our 'better' method will be declared as follows:

C#
public static TEnum GetEnumValue(string val)
    where TEnum : struct, IComparable, IFormattable, IConvertible
{
    var values = Enum.GetValues(typeof(TEnum));
    foreach (TEnum value in values)
    {
        if (val.Equals(value.ToString()))
        {
            return value;
        }
    }
    return default(TEnum);
}

Of course, there can be other types that can work with this method but are in fact not an enumerable type, but this is the best solution available in C# (that I know of). Default statement in case where string value is not found in method will return first defined value of an enum.

The next step will be to add possibility of integers values in strings. For that, we have to cast enum values to type int. We cannot do that with simple casting operation in C# because it is not possible with generic type we defined in our improved method. But we can use IConvertible interface and its ToInt32 method. It requires format provider for casting though. I used CultureInfo.CurrentCulture property which was OK in my application, but this could be a problem in others. It depends where it will be used. Changed method will look like this:

C#
public static TEnum GetEnumValue2(string val)
    where TEnum : struct, IComparable, IFormattable, IConvertible
{
    var values = Enum.GetValues(typeof(TEnum));
    foreach (TEnum value in values)
    {
        if (val.Equals(value.ToString())
            || val == (value.ToInt32(CultureInfo.CurrentCulture)).ToString())
        {
            return value;
        }
    }
    return default(TEnum);
}

This mostly works OK, but this might be a problem when it is used like this:

C#
package.Status = GetEnumValue<PackageStatus>(newStatusString);

Why? Because when newStatusString value is not proper value for an enum, status property will reset to default status value. It might be a problem. Solution might be exception throwing when value is invalid. This would be good for UI. I decided to use custom default value:

C#
package.Status = GetEnumValue(newStatusString, package.Status);

This way, status will not change if value in string is invalid and old value will be assigned.

Finally, I added case insensitivity for string comparison. There are plenty of possibilities to do that in .NET so this is something that should be considered in regards of application which will be using that code. For example, we can do something like this:

C#
public static TEnum GetEnumValue2(string val,TEnum current)
where TEnum : struct, IComparable, IFormattable, IConvertible
{
    var values = Enum.GetValues(typeof(TEnum));
    foreach (TEnum value in values)
    {
        if (val.Equals(value.ToString(), StringComparison.OrdinalIgnoreCase)
            || val == (value.ToInt32(CultureInfo.CurrentCulture)).ToString())
        {
            return value;
        }
    }
    return current;
}

Nice to have feature is to define this method as an extension method for string. This way, we can call it after writing the name of the variable with our string value.

C#
package.Status = newStatusString.GetEnumValue(package.Status);

I prefer to do this that way, because it is more expressive to my coding style. While writing solution for some kind of a problem I think: I want here this value but after mapping it in the following way. With using method GetEnumValue as a plain method not extension, it is in my opinion greater burden for someone who read code (which is mostly me and I always want to make my life easier :) ). But this is the subject of another article.

Anyway, this can be achieved just by adding this keyword and placing method in a separate class.

C#
public static class Extension
{
    public static TEnum GetEnumValue( this string val, TEnum current)
where TEnum : struct, IComparable, IFormattable, IConvertible
    {
        var values = Enum.GetValues(typeof(TEnum));
        foreach (TEnum value in values)
        {
            if (val.Equals(value.ToString(), StringComparison.OrdinalIgnoreCase)
                || val == (value.ToInt32(CultureInfo.CurrentCulture)).ToString())
            {
                return value;
            }
        }
        return current;
    }
}

This is a very simple solution for this particular problem. There are more things that can be changed/improved. You can download the code sample and play with it yourself. :)

Enjoy!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Poland Poland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionEnum.Parse/TryParse Pin
EdGmail2-Mar-14 4:42
EdGmail2-Mar-14 4:42 
AnswerRe: Enum.Parse/TryParse Pin
n.podbielski2-Mar-14 5:14
n.podbielski2-Mar-14 5:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.