Enum Limitation Fix Using a Generic Class and Implicit Casting


/ Published in: C#
Save to your folder(s)

For .net 2.0+ The one limitation to enumerations is revealed when you try to reverse lookup an enumeration value using Enum.Parse(). The 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. Written as a system extension and using implicit casting, the process has been made extremely easy and made the syntax for the parse function even simpler. The process even allows enumeration names starting with a number or the name of a C# keyword as long as the name is preceded by an underscore. The implicit cast from an Enum object to a Enum.Cast object has been deliberately left out to account for single directional assignment, which forces the class to be used properly. An Enum to Cast object lookup would defeat the whole purpose of the class if the implicit operator is used during runtime; for this purpose a user assignment operator of type String is supplied. This simply forces the user to use Cast = Enum.ToString() to parse to a correct object. The ToString() overload for a Cast object returns a Friendly name which replaces all underscores with spaces and even allows double underscores for commas and triple underscores for periods; for this reason, the implicit \"from string\" caster also converts from a friendly name to the proper Enum object. This makes it very handy for enumerating through a list of items for a combo or list box and converting back to the proper object by simply supplying the name of the list item.


Copy this code and paste it in your HTML
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text.RegularExpressions;
  4.  
  5. namespace System
  6. {
  7. /// <summary>
  8. /// A comparer which can be assigned and have it's case sensitivity switched on the fly before a comparision is done.
  9. /// </summary>
  10. public class CaseComparer : IEqualityComparer<string>
  11. {
  12. public bool CaseSensitive = true;
  13.  
  14. public CaseComparer() { }
  15.  
  16. public CaseComparer(bool caseSensitive)
  17. {
  18. this.CaseSensitive = caseSensitive;
  19. }
  20.  
  21. #region IEqualityComparer<string> Members
  22.  
  23. public bool Equals(string x, string y)
  24. {
  25. if (CaseSensitive)
  26. return StringComparer.CurrentCulture.Equals(x, y);
  27. else
  28. return StringComparer.CurrentCultureIgnoreCase.Equals(x, y);
  29. }
  30.  
  31. public int GetHashCode(string obj)
  32. {
  33. return obj.GetHashCode();
  34. }
  35.  
  36. #endregion
  37. }
  38.  
  39. /// <summary>
  40. /// Fixes the "parsing an enum returns an uncertain object" problem which occurs with standard enum objects having the same numeric value.
  41. /// </summary>
  42. /// <typeparam name="EnumType">An Enum object type. All other types are ignored.</typeparam>
  43. public static class Enum<EnumType>
  44. {
  45. private static Dictionary<Type, Dictionary<string, Cast>> nameLists = new Dictionary<Type, Dictionary<string, Cast>>();
  46. private static bool ValidType { get { return (typeof(EnumType).BaseType == typeof(global::System.Enum)); } }
  47. 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);
  48.  
  49. /// <summary>
  50. /// Fixes the "parsing an enum returns an uncertain object" problem which occurs with standard enum objects having the same numeric value.
  51. /// </summary>
  52. public sealed class Cast : IComparable, IFormattable, IConvertible
  53. {
  54. public readonly EnumType Enumeration;
  55. public readonly string RealName;
  56. public bool CaseSensitive = true;
  57. public static readonly Enum<EnumType>.Cast DefaultEnumeration = default(EnumType).ToString();
  58. public Enum<EnumType>.Cast Default { get { return DefaultEnumeration; } }
  59. public bool IsDefault { get { return (Enumeration.Equals(DefaultEnumeration.Enumeration)); } }
  60.  
  61. internal Cast(global::System.Enum enumeration, string realName)
  62. {
  63. this.Enumeration = (EnumType)(object)enumeration;
  64. this.RealName = realName;
  65. }
  66.  
  67. #region IComparable Members
  68.  
  69. int IComparable.CompareTo(object obj)
  70. {
  71. if (obj is global::System.Enum)
  72. return ((Enum)(object)this.Enumeration).CompareTo(obj);
  73. if (obj is Cast)
  74. return Decimal.Compare(Convert.ToInt64(Enumeration), Convert.ToInt64(((Cast)obj).Enumeration));
  75. if (obj is string)
  76. return (CaseSensitive) ? StringComparer.CurrentCulture.Compare(RealName, obj) : StringComparer.CurrentCultureIgnoreCase.Compare(RealName, obj);
  77.  
  78. return -1;
  79. }
  80.  
  81. #endregion
  82.  
  83. #region IFormattable Members
  84.  
  85. public override string ToString()
  86. {
  87. return RealName;
  88. }
  89.  
  90. public string ToString(string format, IFormatProvider formatProvider)
  91. {
  92. if (formatProvider != null)
  93. return RealName.ToString(formatProvider);
  94. return RealName;
  95. }
  96.  
  97. #endregion
  98.  
  99. #region IConvertible Members
  100.  
  101. TypeCode IConvertible.GetTypeCode()
  102. {
  103. return ((Enum)(object)Enumeration).GetTypeCode();
  104. }
  105.  
  106. bool IConvertible.ToBoolean(IFormatProvider provider)
  107. {
  108. return Convert.ToBoolean(Enumeration);
  109. }
  110.  
  111. byte IConvertible.ToByte(IFormatProvider provider)
  112. {
  113. return Convert.ToByte(Enumeration);
  114. }
  115.  
  116. char IConvertible.ToChar(IFormatProvider provider)
  117. {
  118. return Convert.ToChar(Enumeration);
  119. }
  120.  
  121. DateTime IConvertible.ToDateTime(IFormatProvider provider)
  122. {
  123. return Convert.ToDateTime(Enumeration);
  124. }
  125.  
  126. decimal IConvertible.ToDecimal(IFormatProvider provider)
  127. {
  128. return Convert.ToDecimal(Enumeration);
  129. }
  130.  
  131. double IConvertible.ToDouble(IFormatProvider provider)
  132. {
  133. return Convert.ToDouble(Enumeration);
  134. }
  135.  
  136. short IConvertible.ToInt16(IFormatProvider provider)
  137. {
  138. return Convert.ToInt16(Enumeration);
  139. }
  140.  
  141. int IConvertible.ToInt32(IFormatProvider provider)
  142. {
  143. return Convert.ToInt32(Enumeration);
  144. }
  145.  
  146. long IConvertible.ToInt64(IFormatProvider provider)
  147. {
  148. return Convert.ToInt64(Enumeration);
  149. }
  150.  
  151. sbyte IConvertible.ToSByte(IFormatProvider provider)
  152. {
  153. return Convert.ToSByte(Enumeration);
  154. }
  155.  
  156. float IConvertible.ToSingle(IFormatProvider provider)
  157. {
  158. return Convert.ToSingle(Enumeration);
  159. }
  160.  
  161. object IConvertible.ToType(Type conversionType, IFormatProvider provider)
  162. {
  163. return Enumeration.GetType();
  164. }
  165.  
  166. ushort IConvertible.ToUInt16(IFormatProvider provider)
  167. {
  168. return Convert.ToUInt16(Enumeration);
  169. }
  170.  
  171. uint IConvertible.ToUInt32(IFormatProvider provider)
  172. {
  173. return Convert.ToUInt32(Enumeration);
  174. }
  175.  
  176. ulong IConvertible.ToUInt64(IFormatProvider provider)
  177. {
  178. return Convert.ToUInt64(Enumeration);
  179. }
  180.  
  181. string IConvertible.ToString(IFormatProvider provider)
  182. {
  183. return this.ToString(RealName, provider);
  184. }
  185.  
  186. #endregion
  187.  
  188. //underscore-decode name
  189. public string FriendlyName
  190. {
  191. get
  192. {
  193. if (RealName.Contains("_"))
  194. return RealName.Replace("___", ". ").Replace("__", ", ").Replace("_", " ").Trim();
  195. return RealName;
  196. }
  197. }
  198.  
  199. public static implicit operator Cast(string enumName)
  200. {
  201. return Parse(enumName);
  202. }
  203.  
  204. public static implicit operator EnumType(Cast enumerationCast)
  205. {
  206. if (enumerationCast != null)
  207. return enumerationCast.Enumeration;
  208. return DefaultEnumeration.Enumeration;
  209. }
  210.  
  211. }
  212.  
  213. /// <summary>
  214. /// Fixes the "parsing an enum returns an uncertain object" problem which occurs with standard enum objects having the same numeric value.
  215. /// Parse casts a enum name or Cast.FriendlyName to an Enum.Cast object of the proper enum type.
  216. /// </summary>
  217. /// <param name="enumName">The enumeration name to parse and cast to.</param>
  218. /// <param name="ignoreCase">Specifies if the parse should find and cast even if the string case is not exact.</param>
  219. /// <returns>An Enum.Cast object if the the generic type specified is a valid enum type. Otherwise returns null.</returns>
  220. public static Cast Parse(string enumName, bool ignoreCase)
  221. {
  222. if (ValidType)
  223. {
  224. Type enumType = typeof(EnumType);
  225. Dictionary<string, Cast> nameList;
  226.  
  227. //cache or recieve from cache enum name list
  228. if (nameLists.ContainsKey(enumType))
  229. nameList = nameLists[enumType];
  230. else
  231. {
  232. nameList = new Dictionary<string, Cast>(new CaseComparer());
  233. EnumType[] values = (EnumType[])global::System.Enum.GetValues(enumType);
  234. string[] names = global::System.Enum.GetNames(enumType);
  235.  
  236. //store true values
  237. for (int i = 0; i < names.Length; i++)
  238. {
  239. EnumType trueValue = values[i];
  240. Cast cacheItem = new Cast((Enum)(object)trueValue, names[i]);
  241. nameList.Add(names[i], cacheItem);
  242. }
  243.  
  244. //store enum true name list associated with the enum type
  245. nameLists.Add(enumType, nameList);
  246. }
  247.  
  248. //set string compare method of list
  249. CaseComparer comparer = (CaseComparer)nameList.Comparer;
  250. comparer.CaseSensitive = !ignoreCase;
  251.  
  252. string fixedName = enumName.Trim();
  253. //underscore-encode name
  254. if (fixedName.Contains(".") || fixedName.Contains(",") || fixedName.Contains(" "))
  255. fixedName = fixedName.Replace(". ", "___").Replace(".", "___").Replace(", ", "__").Replace(",", "__").Replace(" ", "_").Trim();
  256. //allow enumerations that start with a number or are a keyword, as long as it is preceeded by a single underscore
  257. if (rxStartsWithKeyWord.Match(fixedName).Success)
  258. fixedName = "_" + fixedName;
  259.  
  260. if (nameList.ContainsKey(fixedName))
  261. return nameList[fixedName];
  262.  
  263. return Enum<EnumType>.Cast.DefaultEnumeration;
  264. }
  265. return null;
  266. }
  267.  
  268. /// <summary>
  269. /// Fixes the "parsing an enum returns an uncertain object" problem which occurs with standard enum objects having the same numeric value.
  270. /// Parse (case sensitive) casts a enum name or Cast.FriendlyName to an Enum.Cast object of the proper enum type.
  271. /// </summary>
  272. /// <param name="enumName">The enumeration name to parse and cast to.</param>
  273. /// <returns>An Enum.Cast object if the the generic type specified is a valid enum type. Otherwise returns null.</returns>
  274. public static Cast Parse(string enumName)
  275. {
  276. return Parse(enumName, false);
  277. }
  278.  
  279. public static IList<EnumType> GetValues()
  280. {
  281. IList<EnumType> list = new List<EnumType>();
  282. foreach (object value in Enum.GetValues(typeof(EnumType)))
  283. {
  284. list.Add((EnumType)value);
  285. }
  286. return list;
  287. }
  288. }
  289. }
  290.  
  291. /*
  292. Syntax to use the class, is shown in the following advanced example:
  293. */
  294.  
  295. public class MyClass
  296. {
  297.  
  298. [DefaultValue(eOperation.Custom)]
  299. public enum eOperation
  300. {
  301. Custom=0,
  302. create,
  303. inspect,
  304. edit,
  305. source
  306. }
  307.  
  308. private string operationField;
  309. private Enum<eOperation>.Cast operationEnum;
  310.  
  311. public eOperation operation
  312. {
  313. get { return this.operationEnum; }
  314. set { this.operationText = value.ToString(); }
  315. }
  316.  
  317. public string operationText
  318. {
  319. get { return this.operationField; }
  320. set
  321. {
  322. this.operationEnum = Enum<eOperation>.Parse(value, true);
  323. if (this.operationEnum.IsDefault)
  324. this.operationField = value;
  325. else
  326. this.operationField = this.operationEnum.FriendlyName;
  327. }
  328. }
  329.  
  330. }
  331.  
  332. /*
  333. 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.
  334. */

Report this snippet


Comments

RSS Icon Subscribe to comments

You need to login to post a comment.