|
|
|
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:
|
238
|
|
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 ...
37
38
39 public bool Equals(string x, string y)
40 {
41 if (CaseSensitive)
42 return StringComparer.CurrentCulture.Equals(x, y);
43 else
44 return StringComparer.CurrentCultureIgnoreCase.Equals(x, y);
45 }
46
47 public int GetHashCode(string obj)
48 {
49 return obj.GetHashCode();
50 }
51 #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 ...
82
83
84 int IComparable.CompareTo(object obj)
85 {
86 if (obj is global::System.Enum)
87 return ((Enum)(object)this.Enumeration).CompareTo(obj);
88 if (obj is Cast)
89 return Decimal.Compare(Convert.ToInt64(Enumeration), Convert.ToInt64(((Cast)obj).Enumeration));
90 if (obj is string)
91 return (CaseSensitive) ? StringComparer.CurrentCulture.Compare(RealName, obj) : StringComparer.CurrentCultureIgnoreCase.Compare(RealName, obj);
92
93 return -1;
94 }
95 #endregion
96 #region IFormattable Members ...
97
98
99 public override string ToString()
100 {
101 return RealName;
102 }
103
104 public string ToString(string format, IFormatProvider formatProvider)
105 {
106 if (formatProvider != null)
107 return RealName.ToString(formatProvider);
108 return RealName;
109 }
110 #endregion
111 #region IConvertible Members ...
112
113
114 TypeCode IConvertible.GetTypeCode()
115 {
116 return ((Enum)(object)Enumeration).GetTypeCode();
117 }
118
119 bool IConvertible.ToBoolean(IFormatProvider provider)
120 {
121 return Convert.ToBoolean(Enumeration);
122 }
123
124 byte IConvertible.ToByte(IFormatProvider provider)
125 {
126 return Convert.ToByte(Enumeration);
127 }
128
129 char IConvertible.ToChar(IFormatProvider provider)
130 {
131 return Convert.ToChar(Enumeration);
132 }
133
134 DateTime IConvertible.ToDateTime(IFormatProvider provider)
135 {
136 return Convert.ToDateTime(Enumeration);
137 }
138
139 decimal IConvertible.ToDecimal(IFormatProvider provider)
140 {
141 return Convert.ToDecimal(Enumeration);
142 }
143
144 double IConvertible.ToDouble(IFormatProvider provider)
145 {
146 return Convert.ToDouble(Enumeration);
147 }
148
149 short IConvertible.ToInt16(IFormatProvider provider)
150 {
151 return Convert.ToInt16(Enumeration);
152 }
153
154 int IConvertible.ToInt32(IFormatProvider provider)
155 {
156 return Convert.ToInt32(Enumeration);
157 }
158
159 long IConvertible.ToInt64(IFormatProvider provider)
160 {
161 return Convert.ToInt64(Enumeration);
162 }
163
164 sbyte IConvertible.ToSByte(IFormatProvider provider)
165 {
166 return Convert.ToSByte(Enumeration);
167 }
168
169 float IConvertible.ToSingle(IFormatProvider provider)
170 {
171 return Convert.ToSingle(Enumeration);
172 }
173
174 object IConvertible.ToType(Type conversionType, IFormatProvider provider)
175 {
176 return Enumeration.GetType();
177 }
178
179 ushort IConvertible.ToUInt16(IFormatProvider provider)
180 {
181 return Convert.ToUInt16(Enumeration);
182 }
183
184 uint IConvertible.ToUInt32(IFormatProvider provider)
185 {
186 return Convert.ToUInt32(Enumeration);
187 }
188
189 ulong IConvertible.ToUInt64(IFormatProvider provider)
190 {
191 return Convert.ToUInt64(Enumeration);
192 }
193
194 string IConvertible.ToString(IFormatProvider provider)
195 {
196 return this.ToString(RealName, provider);
197 }
198 #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.
|
|
|