Home
Manage Your Code
Snippet: Enum Limitation Fix Using a Generic Class and Implicit Casting (C#)
Title: Enum Limitation Fix Using a Generic Class and Implicit Casting Language: C#
Description: The Enum.Parse function will return an inconsistent enum object if ever there are two or more enumerations with the same numeric value. This class fixes that problem. Views: 272
Author: Bryan Lyman Date Added: 10/13/2011
Copy Code  
1using System;
2using System.Collections.Generic;
3using System.Text.RegularExpressions;
4
5/*
6For .net 2.0+ The one limitation to enumerations is revealed when you try to reverse lookup an enumeration value 
7using Enum.Parse(). Written as a system extension and using implicit casting, the process has been made extremely 
8easy and made the syntax for the parse function even simpler. The process even allows enumeration names starting 
9with a number or the name of a C# keyword as long as the name is preceded by an underscore. The implicit cast from 
10an Enum object to a Enum.Cast object has been deliberately left out to account for single directional assignment, 
11which forces the class to be used properly. An Enum to Cast object lookup would defeat the whole purpose of the 
12class if the implicit operator is used during runtime; for this purpose a user assignment operator of type String 
13is supplied. This simply forces the user to use Cast = Enum.ToString() to parse to a correct object. The ToString() 
14overload for a Cast object returns a Friendly name which replaces all underscores with spaces and even allows double 
15underscores for commas and triple underscores for periods; for this reason, the implicit "from string" caster also 
16converts from a friendly name to the proper Enum object. This makes it very handy for enumerating through a list of 
17items for a combo or list box and converting back to the proper object by simply supplying the name of the list 
18item.
19*/
20 
21namespace System
22{
23    /// <summary>

24    /// A comparer which can be assigned and have it's case sensitivity switched on the fly before a comparision is done.

25    /// </summary>

26    public class CaseComparer : IEqualityComparer<string>
27    {
28        public bool CaseSensitive = true;
29 
30        public CaseComparer() { }
31 
32        public CaseComparer(bool caseSensitive)
33        {
34            this.CaseSensitive = caseSensitive;
35        }
36         #region IEqualityComparer<string> Members   ...         #endregion
52    }
53 
54    /// <summary>

55    /// Fixes the "parsing an enum returns an uncertain object" problem which occurs with standard enum objects having the same numeric value.

56    /// </summary>

57    /// <typeparam name="EnumType">An Enum object type. All other types are ignored.</typeparam>

58    public static class Enum<EnumType>
59    {
60        private static Dictionary<Type, Dictionary<string, Cast>> nameLists = new Dictionary<Type, Dictionary<string, Cast>>();
61        private static bool ValidType { get { return (typeof(EnumType).BaseType == typeof(global::System.Enum)); } }
62        private static Regex rxStartsWithKeyWord = new Regex(@"^[0-9]|^abstract$|^as$|^base$|^bool$|^break$|^byte$|^case$|^catch$|^char$|^checked$|^class$|^const$|^continue$|^decimal$|^default$|^delegate$|^do$|^double$|^else$|^enum$|^event$|^explicit$|^extern$|^$false|^finally$|^fixed$|^float$|^for$|^foreach$|^goto$|^if$|^implicit$|^in$|^int$|^interface$|^internal$|^is$|^lock$|^long$|^namespace$|^new$|^null$|^object$|^operator$|^out$|^overrride$|^params$|^private$|^protected$|^public$|^readonly$|^ref$|^return$|^sbyte$|^sealed$|^short$|^sizeof$|^stackalloc$|^static$|^string$|^struct$|^switch$|^this$|^thorw$|^true$|^try$|^typeof$|^uint$|^ulong$|^unchecked$|^unsafe$|^ushort$|^using$|^virtual$|^volatile$|^void$|^while$", RegexOptions.Compiled);
63 
64        /// <summary>

65        /// Fixes the "parsing an enum returns an uncertain object" problem which occurs with standard enum objects having the same numeric value.

66        /// </summary>

67        public sealed class Cast : IComparable, IFormattable, IConvertible
68        {
69            public readonly EnumType Enumeration;
70            public readonly string RealName;
71            public bool CaseSensitive = true;
72            public static readonly Enum<EnumType>.Cast DefaultEnumeration = default(EnumType).ToString();
73            public Enum<EnumType>.Cast Default { get { return DefaultEnumeration; } }
74            public bool IsDefault { get { return (Enumeration.Equals(DefaultEnumeration.Enumeration)); } }
75 
76            internal Cast(global::System.Enum enumeration, string realName)
77            {
78                this.Enumeration = (EnumType)(object)enumeration;
79                this.RealName = realName;
80            }
81             #region IComparable Members   ...             #endregion
96             #region IFormattable Members   ...             #endregion
111             #region IConvertible Members   ...             #endregion
199 
200            //underscore-decode name

201            public string FriendlyName
202            {
203                get
204                {
205                    if (RealName.Contains("_"))
206                        return RealName.Replace("___", ". ").Replace("__", ", ").Replace("_", " ").Trim();
207                    return RealName;
208                }
209            }
210 
211            public static implicit operator Cast(string enumName)
212            {
213                return Parse(enumName);
214            }
215 
216            public static implicit operator EnumType(Cast enumerationCast)
217            {
218                if (enumerationCast != null)
219                    return enumerationCast.Enumeration;
220                return DefaultEnumeration.Enumeration;
221            }
222 
223        }
224 
225        /// <summary>

226        /// Fixes the "parsing an enum returns an uncertain object" problem which occurs with standard enum objects having the same numeric value.

227        /// Parse casts a enum name or Cast.FriendlyName to an Enum.Cast object of the proper enum type.

228        /// </summary>

229        /// <param name="enumName">The enumeration name to parse and cast to.</param>

230        /// <param name="ignoreCase">Specifies if the parse should find and cast even if the string case is not exact.</param>

231        /// <returns>An Enum.Cast object if the the generic type specified is a valid enum type. Otherwise returns null.</returns>

232        public static Cast Parse(string enumName, bool ignoreCase)
233        {
234            if (ValidType)
235            {
236                Type enumType = typeof(EnumType);
237                Dictionary<string, Cast> nameList;
238 
239                //cache or recieve from cache enum name list

240                if (nameLists.ContainsKey(enumType))
241                    nameList = nameLists[enumType];
242                else
243                {
244                    nameList = new Dictionary<string, Cast>(new CaseComparer());
245                    EnumType[] values = (EnumType[])global::System.Enum.GetValues(enumType);
246                    string[] names = global::System.Enum.GetNames(enumType);
247 
248                    //store true values

249                    for (int i = 0; i < names.Length; i++)
250                    {
251                        EnumType trueValue = values[i];
252                        Cast cacheItem = new Cast((Enum)(object)trueValue, names[i]);
253                        nameList.Add(names[i], cacheItem);
254                    }
255 
256                    //store enum true name list associated with the enum type

257                    nameLists.Add(enumType, nameList);
258                }
259 
260                //set string compare method of list

261                CaseComparer comparer = (CaseComparer)nameList.Comparer;
262                comparer.CaseSensitive = !ignoreCase;
263 
264                string fixedName = enumName.Trim();
265                //underscore-encode name

266                if (fixedName.Contains(".") || fixedName.Contains(",") || fixedName.Contains(" "))
267                    fixedName = fixedName.Replace(". ", "___").Replace(".", "___").Replace(", ", "__").Replace(",", "__").Replace(" ", "_").Trim();
268                //allow enumerations that start with a number or are a keyword, as long as it is preceeded by a single underscore

269                if (rxStartsWithKeyWord.Match(fixedName).Success)
270                    fixedName = "_" + fixedName;
271 
272                if (nameList.ContainsKey(fixedName))
273                    return nameList[fixedName];
274 
275                return Enum<EnumType>.Cast.DefaultEnumeration;
276            }
277            return null;
278        }
279 
280        /// <summary>

281        /// Fixes the "parsing an enum returns an uncertain object" problem which occurs with standard enum objects having the same numeric value.

282        /// Parse (case sensitive) casts a enum name or Cast.FriendlyName to an Enum.Cast object of the proper enum type.

283        /// </summary>

284        /// <param name="enumName">The enumeration name to parse and cast to.</param>        

285        /// <returns>An Enum.Cast object if the the generic type specified is a valid enum type. Otherwise returns null.</returns>

286        public static Cast Parse(string enumName)
287        {
288            return Parse(enumName, false);
289        }
290 
291        public static IList<EnumType> GetValues()
292        {
293            IList<EnumType> list = new List<EnumType>();
294            foreach (object value in Enum.GetValues(typeof(EnumType)))
295            {
296                list.Add((EnumType)value);
297            }
298            return list;
299        }
300    }
301}
Usage
public class MyClass
{
 
     [DefaultValue(eOperation.Custom)]
     public enum eOperation
     {
         Custom=0,
         create,
         inspect,
         edit,
         source
     }
 
     private string operationField;
     private Enum.Cast operationEnum;
 
     public eOperation operation
     {
         get { return this.operationEnum; }
         set { this.operationText = value.ToString(); }
     }
 
     public string operationText
     {
         get { return this.operationField; }
         set
         {
             this.operationEnum = Enum.Parse(value, true);
             if (this.operationEnum.IsDefault)
                 this.operationField = value;
             else
                 this.operationField = this.operationEnum.FriendlyName;
         }
     }
 
}
Notes
A direct assignment could also be used in the operationText (set) property, but the static Parse() function was used instead to display what is occurring behind the scenes with the implicit operator. Also, the Parse function is used to show how to use the case-insensitive Parse(), since the implicit operator is always case-sensitive.