In on of my projects I had make an interface between database and web service in C# code.
One of problem I had to face was need to cast strings to enumerable types, because of simple fact that database have no idea what is an ‘enum’. Yes simplest mapping between an enumeration and database type is integer. It is simplest but in my idea not 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 valid flow of package delivery flow. For someone that is working on 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 company grows, software grows. And most likely more enumeration types will be created.
So to have best of to worlds, in my opinion this is best to have enumeration in strings and with possibility to cast strings like ‘0’ and ‘RequestReceived’ to RequestReceived enum value.
Nice feature is to make casting also case insensitive. But this is not necessary.
Aside from interfacing with database there are other use case 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:
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:
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 is a few problems with this method. For 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 have too have default value of enum type with integer equivalent of 0. 0 can not be in an enum at all!
For first issue we can add generic type argument with constraints of interfaces implemented by enum types and with struct special constraint.
For second issue we can use default C# keyword. Our ‘better’ method will be declared as following:
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 are can be other types that can work with this method but are in fact not an enumerable types, but this is 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.
Next step will be 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:
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 work ok, but this might be a problem when it is used like this:
package.Status = GetEnumValue<PackageStatus>(newStatusString);
Why? Because when newStatusString value is not proper value for and 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:
package.Status = GetEnumValue(newStatusString, package.Status);
This way status will not change if value in string is inalid 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:
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 have feature is to defined this method as extension method for string. This way we can call it after writing name of the variable with our string value.
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 subject of another article.
Anyway this can be achieved just by adding this keyword and placing method in separate class.
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 very simple solution for this particular problem. There are more thing that can be changed/improved. You can download code sample and play with it yourself 🙂
Enjoy!