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:
- User interfaces input
- Type serialization to JSON and from JSON
- XML serialization
- 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 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:
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:
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:
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:
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:
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.
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.
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!
CodeProject