5 // Miguel de Icaza (miguel@ximian.com)
6 // Nick Drochak (ndrochak@gol.com)
7 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
9 // (C) Ximian, Inc. http://www.ximian.com
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System.Collections;
37 using System.Collections.Generic;
38 using System.Globalization;
39 using System.Runtime.CompilerServices;
40 using System.Runtime.InteropServices;
44 internal struct MonoEnumInfo
47 internal Array values;
48 internal string[] names;
49 internal Dictionary<string, int> name_hash;
51 static Dictionary<Type, MonoEnumInfo> cache;
52 static readonly Dictionary<Type, MonoEnumInfo> global_cache = new Dictionary<Type, MonoEnumInfo> ();
53 static object global_cache_monitor = new object ();
55 [MethodImplAttribute (MethodImplOptions.InternalCall)]
56 private static extern void get_enum_info (Type enumType, out MonoEnumInfo info);
59 // These comparers are needed because enumerations must be compared
60 // using unsigned values so that negative numbers can be looked up
63 internal static SByteComparer sbyte_comparer = new SByteComparer ();
64 internal static ShortComparer short_comparer = new ShortComparer ();
65 internal static IntComparer int_comparer = new IntComparer ();
66 internal static LongComparer long_comparer = new LongComparer ();
68 internal class SByteComparer : IComparer, System.Collections.Generic.IComparer<sbyte>
70 public int Compare (object x, object y)
75 return ((byte) ix) - ((byte) iy);
78 public int Compare (sbyte ix, sbyte iy)
80 return ((byte) ix) - ((byte) iy);
84 internal class ShortComparer : IComparer, System.Collections.Generic.IComparer<short>
86 public int Compare (object x, object y)
91 return ((ushort) ix) - ((ushort) iy);
94 public int Compare (short ix, short iy)
96 return ((ushort) ix) - ((ushort) iy);
100 internal class IntComparer : IComparer, System.Collections.Generic.IComparer<int>
102 public int Compare (object x, object y)
110 if (((uint) ix) < ((uint) iy))
115 public int Compare (int ix, int iy)
120 if (((uint) ix) < ((uint) iy))
126 internal class LongComparer : IComparer, System.Collections.Generic.IComparer<long>
128 public int Compare (object x, object y)
135 if (((ulong) ix) < ((ulong) iy))
140 public int Compare (long ix, long iy)
144 if (((ulong) ix) < ((ulong) iy))
150 private MonoEnumInfo (MonoEnumInfo other)
153 values = other.values;
155 name_hash = other.name_hash;
158 internal static void GetInfo (Type enumType, out MonoEnumInfo info)
160 /* First check the thread-local cache without locking */
161 if (cache != null && cache.TryGetValue (enumType, out info)) {
165 /* Threads could die, so keep a global cache too */
166 lock (global_cache_monitor) {
167 if (global_cache.TryGetValue (enumType, out info)) {
169 cache = new Dictionary<Type, MonoEnumInfo> ();
171 cache [enumType] = info;
176 get_enum_info (enumType, out info);
178 Type et = Enum.GetUnderlyingType (enumType);
179 SortEnums (et, info.values, info.names);
181 if (info.names.Length > 50) {
182 info.name_hash = new Dictionary<string, int> (info.names.Length);
183 for (int i = 0; i < info.names.Length; ++i)
184 info.name_hash [info.names [i]] = i;
186 MonoEnumInfo cached = new MonoEnumInfo (info);
187 lock (global_cache_monitor) {
188 global_cache [enumType] = cached;
192 internal static void SortEnums (Type et, Array values, Array names)
195 if (et == typeof (int))
197 else if (et == typeof (short))
199 else if (et == typeof (sbyte))
201 else if (et == typeof (long))
204 Array.Sort (values, names, ic);
210 public abstract class Enum : ValueType, IComparable, IConvertible, IFormattable
216 // IConvertible methods Start -->
217 public TypeCode GetTypeCode ()
219 return Type.GetTypeCode (GetUnderlyingType (this.GetType ()));
222 bool IConvertible.ToBoolean (IFormatProvider provider)
224 return Convert.ToBoolean (Value, provider);
227 byte IConvertible.ToByte (IFormatProvider provider)
229 return Convert.ToByte (Value, provider);
232 char IConvertible.ToChar (IFormatProvider provider)
234 return Convert.ToChar (Value, provider);
237 DateTime IConvertible.ToDateTime (IFormatProvider provider)
239 return Convert.ToDateTime (Value, provider);
242 decimal IConvertible.ToDecimal (IFormatProvider provider)
244 return Convert.ToDecimal (Value, provider);
247 double IConvertible.ToDouble (IFormatProvider provider)
249 return Convert.ToDouble (Value, provider);
252 short IConvertible.ToInt16 (IFormatProvider provider)
254 return Convert.ToInt16 (Value, provider);
257 int IConvertible.ToInt32 (IFormatProvider provider)
259 return Convert.ToInt32 (Value, provider);
262 long IConvertible.ToInt64 (IFormatProvider provider)
264 return Convert.ToInt64 (Value, provider);
267 sbyte IConvertible.ToSByte (IFormatProvider provider)
269 return Convert.ToSByte (Value, provider);
272 float IConvertible.ToSingle (IFormatProvider provider)
274 return Convert.ToSingle (Value, provider);
277 object IConvertible.ToType (Type targetType, IFormatProvider provider)
279 if (targetType == null)
280 throw new ArgumentNullException ("targetType");
282 return Convert.ToType (this, targetType, provider, false);
285 ushort IConvertible.ToUInt16 (IFormatProvider provider)
287 return Convert.ToUInt16 (Value, provider);
290 uint IConvertible.ToUInt32 (IFormatProvider provider)
292 return Convert.ToUInt32 (Value, provider);
295 ulong IConvertible.ToUInt64 (IFormatProvider provider)
297 return Convert.ToUInt64 (Value, provider);
300 // <-- End IConvertible methods
302 [MethodImplAttribute (MethodImplOptions.InternalCall)]
303 private extern object get_value ();
305 // wrap the icall into a property so we don't hav to use the icall everywhere
306 private object Value {
307 get { return get_value (); }
311 public static Array GetValues (Type enumType)
313 if (enumType == null)
314 throw new ArgumentNullException ("enumType");
316 if (!enumType.IsEnum)
317 throw new ArgumentException ("enumType is not an Enum type.", "enumType");
320 MonoEnumInfo.GetInfo (enumType, out info);
321 return (Array) info.values.Clone ();
325 public static string[] GetNames (Type enumType)
327 if (enumType == null)
328 throw new ArgumentNullException ("enumType");
330 if (!enumType.IsEnum)
331 throw new ArgumentException ("enumType is not an Enum type.");
334 MonoEnumInfo.GetInfo (enumType, out info);
335 return (string []) info.names.Clone ();
339 // The faster, non-boxing version. It must use the special MonoEnumInfo.xxx_comparers
340 // to ensure that we are perfoming bitwise compares, and not signed compares.
342 // It also tries to use the non-boxing version of the various Array.BinarySearch methods
344 static int FindPosition (Type enumType, object value, Array values)
346 switch (Type.GetTypeCode (GetUnderlyingType (enumType))) {
348 sbyte [] sbyte_array = values as sbyte [];
349 return Array.BinarySearch (sbyte_array, (sbyte) value, MonoEnumInfo.sbyte_comparer);
352 byte [] byte_array = values as byte [];
353 return Array.BinarySearch (byte_array, (byte) value);
356 short [] short_array = values as short [];
357 return Array.BinarySearch (short_array, (short)value, MonoEnumInfo.short_comparer);
359 case TypeCode.UInt16:
360 ushort [] ushort_array = values as ushort [];
361 return Array.BinarySearch (ushort_array, (ushort)value);
364 int[] int_array = values as int[];
365 return Array.BinarySearch (int_array, (int)value, MonoEnumInfo.int_comparer);
367 case TypeCode.UInt32:
368 uint[] uint_array = values as uint [];
369 return Array.BinarySearch (uint_array, (uint)value);
372 long [] long_array = values as long [];
373 return Array.BinarySearch (long_array, (long) value, MonoEnumInfo.long_comparer);
375 case TypeCode.UInt64:
376 ulong [] ulong_array = values as ulong [];
377 return Array.BinarySearch (ulong_array, (ulong) value);
380 // This should never happen
381 throw new NotSupportedException ("Unknown underlying enum type");
386 public static string GetName (Type enumType, object value)
388 if (enumType == null)
389 throw new ArgumentNullException ("enumType");
391 throw new ArgumentNullException ("value");
393 if (!enumType.IsEnum)
394 throw new ArgumentException ("enumType is not an Enum type.", "enumType");
397 value = ToObject (enumType, value);
398 MonoEnumInfo.GetInfo (enumType, out info);
400 int i = FindPosition (enumType, value, info.values);
401 return (i >= 0) ? info.names [i] : null;
405 public static bool IsDefined (Type enumType, object value)
407 if (enumType == null)
408 throw new ArgumentNullException ("enumType");
410 throw new ArgumentNullException ("value");
412 if (!enumType.IsEnum)
413 throw new ArgumentException ("enumType is not an Enum type.", "enumType");
416 MonoEnumInfo.GetInfo (enumType, out info);
418 Type vType = value.GetType ();
419 if (vType == typeof(String)) {
420 return ((IList)(info.names)).Contains (value);
421 } else if ((vType == info.utype) || (vType == enumType)) {
422 value = ToObject (enumType, value);
423 MonoEnumInfo.GetInfo (enumType, out info);
425 return FindPosition (enumType, value, info.values) >= 0;
427 throw new ArgumentException("The value parameter is not the correct type. "
428 + "It must be type String or the same type as the underlying type "
433 [MethodImplAttribute (MethodImplOptions.InternalCall)]
434 private static extern Type get_underlying_type (Type enumType);
437 public static Type GetUnderlyingType (Type enumType)
439 if (enumType == null)
440 throw new ArgumentNullException ("enumType");
442 if (!enumType.IsEnum)
443 throw new ArgumentException ("enumType is not an Enum type.", "enumType");
445 return get_underlying_type (enumType);
449 public static object Parse (Type enumType, string value)
451 // Note: Parameters are checked in the other overload
452 return Parse (enumType, value, false);
455 private static int FindName (IDictionary<string, int> name_hash, string [] names, string name, bool ignoreCase)
458 /* For enums with many values, use a hash table */
459 if (name_hash != null) {
461 if (name_hash.TryGetValue (name, out val))
464 for (int i = 0; i < names.Length; ++i) {
465 if (name == names [i])
470 for (int i = 0; i < names.Length; ++i) {
471 if (String.Compare (name, names [i], ignoreCase, CultureInfo.InvariantCulture) == 0)
478 // Helper function for dealing with [Flags]-style enums.
479 private static ulong GetValue (object value, TypeCode typeCode)
485 return (byte) ((sbyte) value);
487 return (ushort) ((short) value);
489 return (uint) ((int) value);
491 return (ulong) ((long) value);
492 case TypeCode.UInt16:
493 return (ushort) value;
494 case TypeCode.UInt32:
496 case TypeCode.UInt64:
497 return (ulong) value;
499 throw new ArgumentException ("typeCode is not a valid type code for an Enum");
503 public static object Parse (Type enumType, string value, bool ignoreCase)
505 if (enumType == null)
506 throw new ArgumentNullException ("enumType");
509 throw new ArgumentNullException ("value");
511 if (!enumType.IsEnum)
512 throw new ArgumentException ("enumType is not an Enum type.", "enumType");
514 value = value.Trim ();
515 if (value.Length == 0)
516 throw new ArgumentException ("An empty string is not considered a valid value.");
519 if (!Parse (enumType, value, ignoreCase, out result))
520 throw new ArgumentException (String.Format ("The requested value '{0}' was not found.", value));
525 static char [] split_char;
527 static bool Parse<TEnum> (Type enumType, string value, bool ignoreCase, out TEnum result)
529 result = default (TEnum);
532 MonoEnumInfo.GetInfo (enumType, out info);
534 // is 'value' a named constant?
535 int loc = FindName (info.name_hash, info.names, value, ignoreCase);
537 result = (TEnum) info.values.GetValue (loc);
541 TypeCode typeCode = ((Enum) info.values.GetValue (0)).GetTypeCode ();
543 // is 'value' a list of named constants?
544 if (value.IndexOf (',') != -1) {
545 if (split_char == null)
546 split_char = new [] { ',' };
547 string [] names = value.Split (split_char);
549 for (int i = 0; i < names.Length; ++i) {
550 loc = FindName (info.name_hash, info.names, names [i].Trim (), ignoreCase);
554 retVal |= GetValue (info.values.GetValue (loc), typeCode);
556 result = (TEnum) ToObject (enumType, retVal);
560 // is 'value' a number?
564 if (!SByte.TryParse (value, out sb))
566 result = (TEnum) ToObject (enumType, sb);
570 if (!Byte.TryParse (value, out b))
572 result = (TEnum) ToObject (enumType, b);
576 if (!Int16.TryParse (value, out i16))
578 result = (TEnum) ToObject (enumType, i16);
580 case TypeCode.UInt16:
582 if (!UInt16.TryParse (value, out u16))
584 result = (TEnum) ToObject (enumType, u16);
588 if (!Int32.TryParse (value, out i32))
590 result = (TEnum) ToObject (enumType, i32);
592 case TypeCode.UInt32:
594 if (!UInt32.TryParse (value, out u32))
596 result = (TEnum) ToObject (enumType, u32);
600 if (!Int64.TryParse (value, out i64))
602 result = (TEnum) ToObject (enumType, i64);
604 case TypeCode.UInt64:
606 if (!UInt64.TryParse (value, out u64))
608 result = (TEnum) ToObject (enumType, u64);
618 public static bool TryParse<TEnum> (string value, out TEnum result) where TEnum : struct
620 return TryParse (value, false, out result);
623 public static bool TryParse<TEnum> (string value, bool ignoreCase, out TEnum result) where TEnum : struct
625 Type tenum_type = typeof (TEnum);
626 if (!tenum_type.IsEnum)
627 throw new ArgumentException("TEnum is not an Enum type.", "enumType");
629 result = default (TEnum);
631 if (value == null || value.Trim ().Length == 0)
634 return Parse (tenum_type, value, ignoreCase, out result);
638 [MethodImplAttribute (MethodImplOptions.InternalCall)]
639 private extern int compare_value_to (object other);
642 /// Compares the enum value with another enum value of the same type.
646 public int CompareTo (object target)
653 thisType = this.GetType ();
654 if (target.GetType() != thisType) {
655 throw new ArgumentException (String.Format (
656 "Object must be the same type as the enum. The type passed in was {0}; the enum type was {1}.",
657 target.GetType(), thisType));
660 return compare_value_to (target);
663 public override string ToString ()
665 return Format (GetType (), Value, "G");
668 [Obsolete("Provider is ignored, just use ToString")]
669 public string ToString (IFormatProvider provider)
671 return ToString ("G", provider);
674 public string ToString (String format)
676 if (format == String.Empty || format == null)
679 return Format (this.GetType (), this.Value, format);
682 [Obsolete("Provider is ignored, just use ToString")]
683 public string ToString (String format, IFormatProvider provider)
685 // provider is not used for Enums
687 if (format == String.Empty || format == null) {
690 return Format (this.GetType(), this.Value, format);
694 public static object ToObject (Type enumType, byte value)
696 return ToObject (enumType, (object)value);
700 public static object ToObject (Type enumType, short value)
702 return ToObject (enumType, (object)value);
706 public static object ToObject (Type enumType, int value)
708 return ToObject (enumType, (object)value);
712 public static object ToObject (Type enumType, long value)
714 return ToObject (enumType, (object)value);
718 [MethodImplAttribute(MethodImplOptions.InternalCall)]
719 public static extern object ToObject (Type enumType, object value);
722 [CLSCompliant (false)]
723 public static object ToObject (Type enumType, sbyte value)
725 return ToObject (enumType, (object)value);
729 [CLSCompliant (false)]
730 public static object ToObject (Type enumType, ushort value)
732 return ToObject (enumType, (object)value);
736 [CLSCompliant (false)]
737 public static object ToObject (Type enumType, uint value)
739 return ToObject (enumType, (object)value);
743 [CLSCompliant (false)]
744 public static object ToObject (Type enumType, ulong value)
746 return ToObject (enumType, (object)value);
749 public override bool Equals (object obj)
751 return DefaultEquals (this, obj);
754 [MethodImplAttribute (MethodImplOptions.InternalCall)]
755 private extern int get_hashcode ();
757 public override int GetHashCode ()
759 return get_hashcode ();
762 private static string FormatSpecifier_X (Type enumType, object value, bool upper)
764 switch (Type.GetTypeCode (enumType)) {
766 return NumberFormatter.NumberToString (upper ? "X2" : "x2", ((sbyte)value), null);
768 return NumberFormatter.NumberToString (upper ? "X2" : "x2", ((byte)value), null);
770 return NumberFormatter.NumberToString (upper ? "X4" : "x4", ((short)value), null);
771 case TypeCode.UInt16:
772 return NumberFormatter.NumberToString (upper ? "X4" : "x4", ((ushort)value), null);
774 return NumberFormatter.NumberToString (upper ? "X8" : "x8", ((int)value), null);
775 case TypeCode.UInt32:
776 return NumberFormatter.NumberToString (upper ? "X8" : "x8", ((uint)value), null);
778 return NumberFormatter.NumberToString (upper ? "X16" : "x16", ((long)value), null);
779 case TypeCode.UInt64:
780 return NumberFormatter.NumberToString (upper ? "X16" : "x16", ((ulong)value), null);
782 throw new Exception ("Invalid type code for enumeration.");
786 static string FormatFlags (Type enumType, object value)
788 string retVal = String.Empty;
790 MonoEnumInfo.GetInfo (enumType, out info);
791 string asString = value.ToString ();
792 if (asString == "0") {
793 retVal = GetName (enumType, value);
798 // This is ugly, yes. We need to handle the different integer
799 // types for enums. If someone else has a better idea, be my guest.
800 switch (((Enum)info.values.GetValue (0)).GetTypeCode ()) {
801 case TypeCode.SByte: {
802 sbyte flags = (sbyte) value;
804 for (int i = info.values.Length - 1; i >= 0; i--) {
805 enumValue = (sbyte) info.values.GetValue (i);
809 if ((flags & enumValue) == enumValue) {
810 retVal = info.names[i] + (retVal == String.Empty ? String.Empty : ", ") + retVal;
814 if (flags != 0) return asString;
817 case TypeCode.Byte: {
818 byte flags = (byte) value;
820 for (int i = info.values.Length - 1; i >= 0; i--) {
821 enumValue = (byte) info.values.GetValue (i);
825 if ((flags & enumValue) == enumValue) {
826 retVal = info.names[i] + (retVal == String.Empty ? String.Empty : ", ") + retVal;
830 if (flags != 0) return asString;
833 case TypeCode.Int16: {
834 short flags = (short) value;
836 for (int i = info.values.Length - 1; i >= 0; i--) {
837 enumValue = (short) info.values.GetValue (i);
841 if ((flags & enumValue) == enumValue) {
842 retVal = info.names[i] + (retVal == String.Empty ? String.Empty : ", ") + retVal;
846 if (flags != 0) return asString;
849 case TypeCode.Int32: {
850 int flags = (int) value;
852 for (int i = info.values.Length - 1; i >= 0; i--) {
853 enumValue = (int) info.values.GetValue (i);
857 if ((flags & enumValue) == enumValue) {
858 retVal = info.names[i] + (retVal == String.Empty ? String.Empty : ", ") + retVal;
862 if (flags != 0) return asString;
865 case TypeCode.UInt16: {
866 ushort flags = (ushort) value;
868 for (int i = info.values.Length - 1; i >= 0; i--) {
869 enumValue = (ushort) info.values.GetValue (i);
873 if ((flags & enumValue) == enumValue) {
874 retVal = info.names[i] + (retVal == String.Empty ? String.Empty : ", ") + retVal;
878 if (flags != 0) return asString;
881 case TypeCode.UInt32: {
882 uint flags = (uint) value;
884 for (int i = info.values.Length - 1; i >= 0; i--) {
885 enumValue = (uint) info.values.GetValue (i);
889 if ((flags & enumValue) == enumValue) {
890 retVal = info.names[i] + (retVal == String.Empty ? String.Empty : ", ") + retVal;
894 if (flags != 0) return asString;
897 case TypeCode.Int64: {
898 long flags = (long) value;
900 for (int i = info.values.Length - 1; i >= 0; i--) {
901 enumValue = (long) info.values.GetValue (i);
905 if ((flags & enumValue) == enumValue) {
906 retVal = info.names[i] + (retVal == String.Empty ? String.Empty : ", ") + retVal;
910 if (flags != 0) return asString;
913 case TypeCode.UInt64: {
914 ulong flags = (ulong) value;
916 for (int i = info.values.Length - 1; i >= 0; i--) {
917 enumValue = (ulong) info.values.GetValue (i);
921 if ((flags & enumValue) == enumValue) {
922 retVal = info.names[i] + (retVal == String.Empty ? String.Empty : ", ") + retVal;
926 if (flags != 0) return asString;
931 if (retVal == String.Empty)
938 public static string Format (Type enumType, object value, string format)
940 if (enumType == null)
941 throw new ArgumentNullException ("enumType");
943 throw new ArgumentNullException ("value");
945 throw new ArgumentNullException ("format");
947 if (!enumType.IsEnum)
948 throw new ArgumentException ("enumType is not an Enum type.", "enumType");
950 Type vType = value.GetType();
951 Type underlyingType = Enum.GetUnderlyingType (enumType);
953 if (vType != enumType)
954 throw new ArgumentException (string.Format(
955 "Object must be the same type as the enum. The type" +
956 " passed in was {0}; the enum type was {1}.",
957 vType.FullName, enumType.FullName));
958 } else if (vType != underlyingType) {
959 throw new ArgumentException (string.Format (
960 "Enum underlying type and the object must be the same type" +
961 " or object. Type passed in was {0}; the enum underlying" +
962 " type was {1}.", vType.FullName, underlyingType.FullName));
965 if (format.Length == 1) {
966 switch (format [0]) {
969 return FormatFlags (enumType, value);
972 if (!enumType.IsDefined (typeof(FlagsAttribute), false))
973 return GetName (enumType, value) ?? value.ToString ();
977 return FormatSpecifier_X (enumType, value, true);
979 return FormatSpecifier_X (enumType, value, false);
983 value = ((Enum) value).Value;
985 return value.ToString ();
989 throw new FormatException ("Format String can be only \"G\",\"g\",\"X\"," +
990 "\"x\",\"F\",\"f\",\"D\" or \"d\".");
994 public bool HasFlag (Enum flag)
996 var val = get_value ();
997 ulong mvalue = GetValue (val, Type.GetTypeCode (val.GetType ()));
998 ulong fvalue = GetValue (flag, Type.GetTypeCode (flag.GetType ()));
1000 return ((mvalue & fvalue) == fvalue);