1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System
.Diagnostics
;
6 using System
.Diagnostics
.CodeAnalysis
;
7 using System
.Globalization
;
8 using System
.Reflection
;
9 using System
.Runtime
.CompilerServices
;
10 using Internal
.Runtime
.CompilerServices
;
13 using CorElementType
= System
.Runtime
.RuntimeImports
.RhCorElementType
;
14 using RuntimeType
= System
.Type
;
15 using EnumInfo
= Internal
.Runtime
.Augments
.EnumInfo
;
18 // The code below includes partial support for float/double and
19 // pointer sized enums.
21 // The type loader does not prohibit such enums, and older versions of
22 // the ECMA spec include them as possible enum types.
24 // However there are many things broken throughout the stack for
25 // float/double/intptr/uintptr enums. There was a conscious decision
26 // made to not fix the whole stack to work well for them because of
27 // the right behavior is often unclear, and it is hard to test and
28 // very low value because of such enums cannot be expressed in C#.
33 [System
.Runtime
.CompilerServices
.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
34 public abstract partial class Enum
: ValueType
, IComparable
, IFormattable
, IConvertible
36 #region Private Constants
37 private const char EnumSeparatorChar
= ',';
40 #region Private Static Methods
42 private string ValueToString()
44 ref byte data
= ref this.GetRawData();
45 return (InternalGetCorElementType()) switch
47 CorElementType
.ELEMENT_TYPE_I1
=> Unsafe
.As
<byte, sbyte>(ref data
).ToString(),
48 CorElementType
.ELEMENT_TYPE_U1
=> data
.ToString(),
49 CorElementType
.ELEMENT_TYPE_BOOLEAN
=> Unsafe
.As
<byte, bool>(ref data
).ToString(),
50 CorElementType
.ELEMENT_TYPE_I2
=> Unsafe
.As
<byte, short>(ref data
).ToString(),
51 CorElementType
.ELEMENT_TYPE_U2
=> Unsafe
.As
<byte, ushort>(ref data
).ToString(),
52 CorElementType
.ELEMENT_TYPE_CHAR
=> Unsafe
.As
<byte, char>(ref data
).ToString(),
53 CorElementType
.ELEMENT_TYPE_I4
=> Unsafe
.As
<byte, int>(ref data
).ToString(),
54 CorElementType
.ELEMENT_TYPE_U4
=> Unsafe
.As
<byte, uint>(ref data
).ToString(),
55 CorElementType
.ELEMENT_TYPE_R4
=> Unsafe
.As
<byte, float>(ref data
).ToString(),
56 CorElementType
.ELEMENT_TYPE_I8
=> Unsafe
.As
<byte, long>(ref data
).ToString(),
57 CorElementType
.ELEMENT_TYPE_U8
=> Unsafe
.As
<byte, ulong>(ref data
).ToString(),
58 CorElementType
.ELEMENT_TYPE_R8
=> Unsafe
.As
<byte, double>(ref data
).ToString(),
59 CorElementType
.ELEMENT_TYPE_I
=> Unsafe
.As
<byte, IntPtr
>(ref data
).ToString(),
60 CorElementType
.ELEMENT_TYPE_U
=> Unsafe
.As
<byte, UIntPtr
>(ref data
).ToString(),
61 _
=> throw new InvalidOperationException(SR
.InvalidOperation_UnknownEnumType
),
65 private string ValueToHexString()
67 ref byte data
= ref this.GetRawData();
68 switch (InternalGetCorElementType())
70 case CorElementType
.ELEMENT_TYPE_I1
:
71 case CorElementType
.ELEMENT_TYPE_U1
:
72 return data
.ToString("X2", null);
73 case CorElementType
.ELEMENT_TYPE_BOOLEAN
:
74 return Convert
.ToByte(Unsafe
.As
<byte, bool>(ref data
)).ToString("X2", null);
75 case CorElementType
.ELEMENT_TYPE_I2
:
76 case CorElementType
.ELEMENT_TYPE_U2
:
77 case CorElementType
.ELEMENT_TYPE_CHAR
:
78 return Unsafe
.As
<byte, ushort>(ref data
).ToString("X4", null);
79 case CorElementType
.ELEMENT_TYPE_I4
:
80 case CorElementType
.ELEMENT_TYPE_U4
:
81 return Unsafe
.As
<byte, uint>(ref data
).ToString("X8", null);
82 case CorElementType
.ELEMENT_TYPE_I8
:
83 case CorElementType
.ELEMENT_TYPE_U8
:
84 return Unsafe
.As
<byte, ulong>(ref data
).ToString("X16", null);
86 throw new InvalidOperationException(SR
.InvalidOperation_UnknownEnumType
);
90 private static string ValueToHexString(object value)
92 return (Convert
.GetTypeCode(value)) switch
94 TypeCode
.SByte
=> ((byte)(sbyte)value).ToString("X2", null),
95 TypeCode
.Byte
=> ((byte)value).ToString("X2", null),
96 TypeCode
.Boolean
=> Convert
.ToByte((bool)value).ToString("X2", null), // direct cast from bool to byte is not allowed
97 TypeCode
.Int16
=> ((ushort)(short)value).ToString("X4", null),
98 TypeCode
.UInt16
=> ((ushort)value).ToString("X4", null),
99 TypeCode
.Char
=> ((ushort)(char)value).ToString("X4", null),
100 TypeCode
.UInt32
=> ((uint)value).ToString("X8", null),
101 TypeCode
.Int32
=> ((uint)(int)value).ToString("X8", null),
102 TypeCode
.UInt64
=> ((ulong)value).ToString("X16", null),
103 TypeCode
.Int64
=> ((ulong)(long)value).ToString("X16", null),
104 _
=> throw new InvalidOperationException(SR
.InvalidOperation_UnknownEnumType
),
108 internal static string? GetEnumName(RuntimeType enumType
, ulong ulValue
)
110 return GetEnumName(GetEnumInfo(enumType
), ulValue
);
113 private static string? GetEnumName(EnumInfo enumInfo
, ulong ulValue
)
115 int index
= Array
.BinarySearch(enumInfo
.Values
, ulValue
);
118 return enumInfo
.Names
[index
];
121 return null; // return null so the caller knows to .ToString() the input
124 private static string? InternalFormat(RuntimeType enumType
, ulong value)
126 EnumInfo enumInfo
= GetEnumInfo(enumType
);
128 if (!enumInfo
.HasFlagsAttribute
)
130 return GetEnumName(enumInfo
, value);
132 else // These are flags OR'ed together (We treat everything as unsigned types)
134 return InternalFlagsFormat(enumType
, enumInfo
, value);
138 private static string? InternalFlagsFormat(RuntimeType enumType
, ulong result
)
140 return InternalFlagsFormat(enumType
, GetEnumInfo(enumType
), result
);
143 private static string? InternalFlagsFormat(RuntimeType enumType
, EnumInfo enumInfo
, ulong resultValue
)
145 Debug
.Assert(enumType
!= null);
147 string[] names
= enumInfo
.Names
;
148 ulong[] values
= enumInfo
.Values
;
149 Debug
.Assert(names
.Length
== values
.Length
);
151 // Values are sorted, so if the incoming value is 0, we can check to see whether
152 // the first entry matches it, in which case we can return its name; otherwise,
153 // we can just return "0".
154 if (resultValue
== 0)
156 return values
.Length
> 0 && values
[0] == 0 ?
161 // With a ulong result value, regardless of the enum's base type, the maximum
162 // possible number of consistent name/values we could have is 64, since every
163 // value is made up of one or more bits, and when we see values and incorporate
164 // their names, we effectively switch off those bits.
165 Span
<int> foundItems
= stackalloc int[64];
167 // Walk from largest to smallest. It's common to have a flags enum with a single
168 // value that matches a single entry, in which case we can just return the existing
170 int index
= values
.Length
- 1;
173 if (values
[index
] == resultValue
)
178 if (values
[index
] < resultValue
)
186 // Now look for multiple matches, storing the indices of the values
188 int resultLength
= 0, foundItemsCount
= 0;
191 ulong currentValue
= values
[index
];
192 if (index
== 0 && currentValue
== 0)
197 if ((resultValue
& currentValue
) == currentValue
)
199 resultValue
-= currentValue
;
200 foundItems
[foundItemsCount
++] = index
;
201 resultLength
= checked(resultLength
+ names
[index
].Length
);
207 // If we exhausted looking through all the values and we still have
208 // a non-zero result, we couldn't match the result to only named values.
209 // In that case, we return null and let the call site just generate
210 // a string for the integral value.
211 if (resultValue
!= 0)
216 // We know what strings to concatenate. Do so.
218 Debug
.Assert(foundItemsCount
> 0);
219 const int SeparatorStringLength
= 2; // ", "
220 string result
= string.FastAllocateString(checked(resultLength
+ (SeparatorStringLength
* (foundItemsCount
- 1))));
222 Span
<char> resultSpan
= new Span
<char>(ref result
.GetRawStringData(), result
.Length
);
223 string name
= names
[foundItems
[--foundItemsCount
]];
224 name
.AsSpan().CopyTo(resultSpan
);
225 resultSpan
= resultSpan
.Slice(name
.Length
);
226 while (--foundItemsCount
>= 0)
228 resultSpan
[0] = EnumSeparatorChar
;
230 resultSpan
= resultSpan
.Slice(2);
232 name
= names
[foundItems
[foundItemsCount
]];
233 name
.AsSpan().CopyTo(resultSpan
);
234 resultSpan
= resultSpan
.Slice(name
.Length
);
236 Debug
.Assert(resultSpan
.IsEmpty
);
241 internal static ulong ToUInt64(object value)
243 // Helper function to silently convert the value to UInt64 from the other base types for enum without throwing an exception.
244 // This is need since the Convert functions do overflow checks.
245 TypeCode typeCode
= Convert
.GetTypeCode(value);
246 ulong result
= typeCode
switch
248 TypeCode
.SByte
=> (ulong)(sbyte)value,
249 TypeCode
.Byte
=> (byte)value,
250 TypeCode
.Boolean
=> Convert
.ToByte((bool)value), // direct cast from bool to byte is not allowed
251 TypeCode
.Int16
=> (ulong)(short)value,
252 TypeCode
.UInt16
=> (ushort)value,
253 TypeCode
.Char
=> (ushort)(char)value,
254 TypeCode
.UInt32
=> (uint)value,
255 TypeCode
.Int32
=> (ulong)(int)value,
256 TypeCode
.UInt64
=> (ulong)value,
257 TypeCode
.Int64
=> (ulong)(long)value,
258 _
=> throw new InvalidOperationException(SR
.InvalidOperation_UnknownEnumType
),
265 #region Public Static Methods
266 public static object Parse(Type enumType
, string value) =>
267 Parse(enumType
, value, ignoreCase
: false);
269 public static object Parse(Type enumType
, string value, bool ignoreCase
)
271 bool success
= TryParse(enumType
, value, ignoreCase
, throwOnFailure
: true, out object? result
);
272 Debug
.Assert(success
);
276 public static TEnum Parse
<TEnum
>(string value) where TEnum
: struct =>
277 Parse
<TEnum
>(value, ignoreCase
: false);
279 public static TEnum Parse
<TEnum
>(string value, bool ignoreCase
) where TEnum
: struct
281 bool success
= TryParse
<TEnum
>(value, ignoreCase
, throwOnFailure
: true, out TEnum result
);
282 Debug
.Assert(success
);
286 public static bool TryParse(Type enumType
, string? value, out object? result
) =>
287 TryParse(enumType
, value, ignoreCase
: false, out result
);
289 public static bool TryParse(Type enumType
, string? value, bool ignoreCase
, out object? result
) =>
290 TryParse(enumType
, value, ignoreCase
, throwOnFailure
: false, out result
);
292 private static bool TryParse(Type enumType
, string? value, bool ignoreCase
, bool throwOnFailure
, out object? result
)
294 // Validation on the enum type itself. Failures here are considered non-parsing failures
295 // and thus always throw rather than returning false.
296 RuntimeType rt
= ValidateRuntimeType(enumType
);
298 ReadOnlySpan
<char> valueSpan
= value.AsSpan().TrimStart();
299 if (valueSpan
.Length
== 0)
303 throw value == null ?
304 new ArgumentNullException(nameof(value)) :
305 new ArgumentException(SR
.Arg_MustContainEnumInfo
, nameof(value));
315 switch (Type
.GetTypeCode(rt
))
318 parsed
= TryParseInt32Enum(rt
, value, valueSpan
, sbyte.MinValue
, sbyte.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.SByte
, out intResult
);
319 result
= parsed
? InternalBoxEnum(rt
, intResult
) : null;
323 parsed
= TryParseInt32Enum(rt
, value, valueSpan
, short.MinValue
, short.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.Int16
, out intResult
);
324 result
= parsed
? InternalBoxEnum(rt
, intResult
) : null;
328 parsed
= TryParseInt32Enum(rt
, value, valueSpan
, int.MinValue
, int.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.Int32
, out intResult
);
329 result
= parsed
? InternalBoxEnum(rt
, intResult
) : null;
333 parsed
= TryParseUInt32Enum(rt
, value, valueSpan
, byte.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.Byte
, out uintResult
);
334 result
= parsed
? InternalBoxEnum(rt
, uintResult
) : null;
337 case TypeCode
.UInt16
:
338 parsed
= TryParseUInt32Enum(rt
, value, valueSpan
, ushort.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.UInt16
, out uintResult
);
339 result
= parsed
? InternalBoxEnum(rt
, uintResult
) : null;
342 case TypeCode
.UInt32
:
343 parsed
= TryParseUInt32Enum(rt
, value, valueSpan
, uint.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.UInt32
, out uintResult
);
344 result
= parsed
? InternalBoxEnum(rt
, uintResult
) : null;
348 parsed
= TryParseInt64Enum(rt
, value, valueSpan
, ignoreCase
, throwOnFailure
, out long longResult
);
349 result
= parsed
? InternalBoxEnum(rt
, longResult
) : null;
352 case TypeCode
.UInt64
:
353 parsed
= TryParseUInt64Enum(rt
, value, valueSpan
, ignoreCase
, throwOnFailure
, out ulong ulongResult
);
354 result
= parsed
? InternalBoxEnum(rt
, (long)ulongResult
) : null;
358 return TryParseRareEnum(rt
, value, valueSpan
, ignoreCase
, throwOnFailure
, out result
);
362 public static bool TryParse
<TEnum
>(string? value, out TEnum result
) where TEnum
: struct =>
363 TryParse
<TEnum
>(value, ignoreCase
: false, out result
);
365 public static bool TryParse
<TEnum
>(string? value, bool ignoreCase
, out TEnum result
) where TEnum
: struct =>
366 TryParse
<TEnum
>(value, ignoreCase
, throwOnFailure
: false, out result
);
368 private static bool TryParse
<TEnum
>(string? value, bool ignoreCase
, bool throwOnFailure
, out TEnum result
) where TEnum
: struct
370 // Validation on the enum type itself. Failures here are considered non-parsing failures
371 // and thus always throw rather than returning false.
372 if (!typeof(TEnum
).IsEnum
)
374 throw new ArgumentException(SR
.Arg_MustBeEnum
, nameof(TEnum
));
377 ReadOnlySpan
<char> valueSpan
= value.AsSpan().TrimStart();
378 if (valueSpan
.Length
== 0)
382 throw value == null ?
383 new ArgumentNullException(nameof(value)) :
384 new ArgumentException(SR
.Arg_MustContainEnumInfo
, nameof(value));
393 RuntimeType rt
= (RuntimeType
)typeof(TEnum
);
395 switch (Type
.GetTypeCode(typeof(TEnum
)))
398 parsed
= TryParseInt32Enum(rt
, value, valueSpan
, sbyte.MinValue
, sbyte.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.SByte
, out intResult
);
399 sbyte sbyteResult
= (sbyte)intResult
;
400 result
= Unsafe
.As
<sbyte, TEnum
>(ref sbyteResult
);
404 parsed
= TryParseInt32Enum(rt
, value, valueSpan
, short.MinValue
, short.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.Int16
, out intResult
);
405 short shortResult
= (short)intResult
;
406 result
= Unsafe
.As
<short, TEnum
>(ref shortResult
);
410 parsed
= TryParseInt32Enum(rt
, value, valueSpan
, int.MinValue
, int.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.Int32
, out intResult
);
411 result
= Unsafe
.As
<int, TEnum
>(ref intResult
);
415 parsed
= TryParseUInt32Enum(rt
, value, valueSpan
, byte.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.Byte
, out uintResult
);
416 byte byteResult
= (byte)uintResult
;
417 result
= Unsafe
.As
<byte, TEnum
>(ref byteResult
);
420 case TypeCode
.UInt16
:
421 parsed
= TryParseUInt32Enum(rt
, value, valueSpan
, ushort.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.UInt16
, out uintResult
);
422 ushort ushortResult
= (ushort)uintResult
;
423 result
= Unsafe
.As
<ushort, TEnum
>(ref ushortResult
);
426 case TypeCode
.UInt32
:
427 parsed
= TryParseUInt32Enum(rt
, value, valueSpan
, uint.MaxValue
, ignoreCase
, throwOnFailure
, TypeCode
.UInt32
, out uintResult
);
428 result
= Unsafe
.As
<uint, TEnum
>(ref uintResult
);
432 parsed
= TryParseInt64Enum(rt
, value, valueSpan
, ignoreCase
, throwOnFailure
, out long longResult
);
433 result
= Unsafe
.As
<long, TEnum
>(ref longResult
);
436 case TypeCode
.UInt64
:
437 parsed
= TryParseUInt64Enum(rt
, value, valueSpan
, ignoreCase
, throwOnFailure
, out ulong ulongResult
);
438 result
= Unsafe
.As
<ulong, TEnum
>(ref ulongResult
);
442 parsed
= TryParseRareEnum(rt
, value, valueSpan
, ignoreCase
, throwOnFailure
, out object? objectResult
);
443 result
= parsed
? (TEnum
)objectResult
! : default;
448 /// <summary>Tries to parse the value of an enum with known underlying types that fit in an Int32 (Int32, Int16, and SByte).</summary>
449 private static bool TryParseInt32Enum(
450 RuntimeType enumType
, string? originalValueString
, ReadOnlySpan
<char> value, int minInclusive
, int maxInclusive
, bool ignoreCase
, bool throwOnFailure
, TypeCode type
, out int result
)
453 enumType
.GetEnumUnderlyingType() == typeof(sbyte) ||
454 enumType
.GetEnumUnderlyingType() == typeof(short) ||
455 enumType
.GetEnumUnderlyingType() == typeof(int));
457 Number
.ParsingStatus status
= default;
458 if (StartsNumber(value[0]))
460 status
= Number
.TryParseInt32IntegerStyle(value, NumberStyles
.AllowLeadingSign
| NumberStyles
.AllowTrailingWhite
, CultureInfo
.InvariantCulture
.NumberFormat
, out result
);
461 if (status
== Number
.ParsingStatus
.OK
)
463 if ((uint)(result
- minInclusive
) <= (uint)(maxInclusive
- minInclusive
))
468 status
= Number
.ParsingStatus
.Overflow
;
472 if (status
== Number
.ParsingStatus
.Overflow
)
476 Number
.ThrowOverflowException(type
);
479 else if (TryParseByName(enumType
, originalValueString
, value, ignoreCase
, throwOnFailure
, out ulong ulongResult
))
481 result
= (int)ulongResult
;
482 Debug
.Assert(result
>= minInclusive
&& result
<= maxInclusive
);
490 /// <summary>Tries to parse the value of an enum with known underlying types that fit in a UInt32 (UInt32, UInt16, and Byte).</summary>
491 private static bool TryParseUInt32Enum(RuntimeType enumType
, string? originalValueString
, ReadOnlySpan
<char> value, uint maxInclusive
, bool ignoreCase
, bool throwOnFailure
, TypeCode type
, out uint result
)
494 enumType
.GetEnumUnderlyingType() == typeof(byte) ||
495 enumType
.GetEnumUnderlyingType() == typeof(ushort) ||
496 enumType
.GetEnumUnderlyingType() == typeof(uint));
498 Number
.ParsingStatus status
= default;
499 if (StartsNumber(value[0]))
501 status
= Number
.TryParseUInt32IntegerStyle(value, NumberStyles
.AllowLeadingSign
| NumberStyles
.AllowTrailingWhite
, CultureInfo
.InvariantCulture
.NumberFormat
, out result
);
502 if (status
== Number
.ParsingStatus
.OK
)
504 if (result
<= maxInclusive
)
509 status
= Number
.ParsingStatus
.Overflow
;
513 if (status
== Number
.ParsingStatus
.Overflow
)
517 Number
.ThrowOverflowException(type
);
520 else if (TryParseByName(enumType
, originalValueString
, value, ignoreCase
, throwOnFailure
, out ulong ulongResult
))
522 result
= (uint)ulongResult
;
523 Debug
.Assert(result
<= maxInclusive
);
531 /// <summary>Tries to parse the value of an enum with Int64 as the underlying type.</summary>
532 private static bool TryParseInt64Enum(RuntimeType enumType
, string? originalValueString
, ReadOnlySpan
<char> value, bool ignoreCase
, bool throwOnFailure
, out long result
)
534 Debug
.Assert(enumType
.GetEnumUnderlyingType() == typeof(long));
536 Number
.ParsingStatus status
= default;
537 if (StartsNumber(value[0]))
539 status
= Number
.TryParseInt64IntegerStyle(value, NumberStyles
.AllowLeadingSign
| NumberStyles
.AllowTrailingWhite
, CultureInfo
.InvariantCulture
.NumberFormat
, out result
);
540 if (status
== Number
.ParsingStatus
.OK
)
546 if (status
== Number
.ParsingStatus
.Overflow
)
550 Number
.ThrowOverflowException(TypeCode
.Int64
);
553 else if (TryParseByName(enumType
, originalValueString
, value, ignoreCase
, throwOnFailure
, out ulong ulongResult
))
555 result
= (long)ulongResult
;
563 /// <summary>Tries to parse the value of an enum with UInt64 as the underlying type.</summary>
564 private static bool TryParseUInt64Enum(RuntimeType enumType
, string? originalValueString
, ReadOnlySpan
<char> value, bool ignoreCase
, bool throwOnFailure
, out ulong result
)
566 Debug
.Assert(enumType
.GetEnumUnderlyingType() == typeof(ulong));
568 Number
.ParsingStatus status
= default;
569 if (StartsNumber(value[0]))
571 status
= Number
.TryParseUInt64IntegerStyle(value, NumberStyles
.AllowLeadingSign
| NumberStyles
.AllowTrailingWhite
, CultureInfo
.InvariantCulture
.NumberFormat
, out result
);
572 if (status
== Number
.ParsingStatus
.OK
)
578 if (status
== Number
.ParsingStatus
.Overflow
)
582 Number
.ThrowOverflowException(TypeCode
.UInt64
);
585 else if (TryParseByName(enumType
, originalValueString
, value, ignoreCase
, throwOnFailure
, out result
))
594 /// <summary>Tries to parse the value of an enum with an underlying type that can't be expressed in C# (e.g. char, bool, double, etc.)</summary>
595 private static bool TryParseRareEnum(RuntimeType enumType
, string? originalValueString
, ReadOnlySpan
<char> value, bool ignoreCase
, bool throwOnFailure
, [NotNullWhen(true)] out object? result
)
598 enumType
.GetEnumUnderlyingType() != typeof(sbyte) &&
599 enumType
.GetEnumUnderlyingType() != typeof(byte) &&
600 enumType
.GetEnumUnderlyingType() != typeof(short) &&
601 enumType
.GetEnumUnderlyingType() != typeof(ushort) &&
602 enumType
.GetEnumUnderlyingType() != typeof(int) &&
603 enumType
.GetEnumUnderlyingType() != typeof(uint) &&
604 enumType
.GetEnumUnderlyingType() != typeof(long) &&
605 enumType
.GetEnumUnderlyingType() != typeof(ulong),
606 "Should only be used when parsing enums with rare underlying types, those that can't be expressed in C#.");
608 if (StartsNumber(value[0]))
610 Type underlyingType
= GetUnderlyingType(enumType
);
613 result
= ToObject(enumType
, Convert
.ChangeType(value.ToString(), underlyingType
, CultureInfo
.InvariantCulture
)!);
616 catch (FormatException
)
618 // We need to Parse this as a String instead. There are cases
619 // when you tlbimp enums that can have values of the form "3D".
621 catch when (!throwOnFailure
)
628 if (TryParseByName(enumType
, originalValueString
, value, ignoreCase
, throwOnFailure
, out ulong ulongResult
))
632 result
= ToObject(enumType
, ulongResult
);
635 catch when (!throwOnFailure
) { }
642 private static bool TryParseByName(RuntimeType enumType
, string? originalValueString
, ReadOnlySpan
<char> value, bool ignoreCase
, bool throwOnFailure
, out ulong result
)
644 // Find the field. Let's assume that these are always static classes because the class is an enum.
645 EnumInfo enumInfo
= GetEnumInfo(enumType
);
646 string[] enumNames
= enumInfo
.Names
;
647 ulong[] enumValues
= enumInfo
.Values
;
650 ulong localResult
= 0;
651 while (value.Length
> 0)
653 // Find the next separator.
654 ReadOnlySpan
<char> subvalue
;
655 int endIndex
= value.IndexOf(EnumSeparatorChar
);
658 // No next separator; use the remainder as the next value.
659 subvalue
= value.Trim();
662 else if (endIndex
!= value.Length
- 1)
664 // Found a separator before the last char.
665 subvalue
= value.Slice(0, endIndex
).Trim();
666 value = value.Slice(endIndex
+ 1);
670 // Last char was a separator, which is invalid.
675 // Try to match this substring against each enum name
676 bool success
= false;
679 for (int i
= 0; i
< enumNames
.Length
; i
++)
681 if (subvalue
.EqualsOrdinalIgnoreCase(enumNames
[i
]))
683 localResult
|= enumValues
[i
];
691 for (int i
= 0; i
< enumNames
.Length
; i
++)
693 if (subvalue
.EqualsOrdinal(enumNames
[i
]))
695 localResult
|= enumValues
[i
];
711 result
= localResult
;
717 throw new ArgumentException(SR
.Format(SR
.Arg_EnumValueNotFound
, originalValueString
));
724 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
725 private static bool StartsNumber(char c
) => char.IsInRange(c
, '0', '9') || c
== '-' || c
== '+';
727 public static object ToObject(Type enumType
, object value)
730 throw new ArgumentNullException(nameof(value));
732 // Delegate rest of error checking to the other functions
733 TypeCode typeCode
= Convert
.GetTypeCode(value);
735 return typeCode
switch
737 TypeCode
.Int32
=> ToObject(enumType
, (int)value),
738 TypeCode
.SByte
=> ToObject(enumType
, (sbyte)value),
739 TypeCode
.Int16
=> ToObject(enumType
, (short)value),
740 TypeCode
.Int64
=> ToObject(enumType
, (long)value),
741 TypeCode
.UInt32
=> ToObject(enumType
, (uint)value),
742 TypeCode
.Byte
=> ToObject(enumType
, (byte)value),
743 TypeCode
.UInt16
=> ToObject(enumType
, (ushort)value),
744 TypeCode
.UInt64
=> ToObject(enumType
, (ulong)value),
745 TypeCode
.Char
=> ToObject(enumType
, (char)value),
746 TypeCode
.Boolean
=> ToObject(enumType
, (bool)value),
747 _
=> throw new ArgumentException(SR
.Arg_MustBeEnumBaseTypeOrEnum
, nameof(value)),
751 public static string Format(Type enumType
, object value, string format
)
753 RuntimeType rtType
= ValidateRuntimeType(enumType
);
756 throw new ArgumentNullException(nameof(value));
759 throw new ArgumentNullException(nameof(format
));
761 // If the value is an Enum then we need to extract the underlying value from it
762 Type valueType
= value.GetType();
763 if (valueType
.IsEnum
)
765 if (!valueType
.IsEquivalentTo(enumType
))
766 throw new ArgumentException(SR
.Format(SR
.Arg_EnumAndObjectMustBeSameType
, valueType
, enumType
));
768 if (format
.Length
!= 1)
770 // all acceptable format string are of length 1
771 throw new FormatException(SR
.Format_InvalidEnumFormatSpecification
);
773 return ((Enum
)value).ToString(format
);
776 // The value must be of the same type as the Underlying type of the Enum
777 Type underlyingType
= GetUnderlyingType(enumType
);
778 if (valueType
!= underlyingType
)
780 throw new ArgumentException(SR
.Format(SR
.Arg_EnumFormatUnderlyingTypeAndObjectMustBeSameType
, valueType
, underlyingType
));
783 if (format
.Length
== 1)
789 return GetEnumName(rtType
, ToUInt64(value)) ?? value.ToString()!;
793 return value.ToString()!;
797 return ValueToHexString(value);
801 return InternalFlagsFormat(rtType
, ToUInt64(value)) ?? value.ToString()!;
805 throw new FormatException(SR
.Format_InvalidEnumFormatSpecification
);
809 #region Private Methods
810 internal object GetValue()
812 ref byte data
= ref this.GetRawData();
813 return (InternalGetCorElementType()) switch
815 CorElementType
.ELEMENT_TYPE_I1
=> Unsafe
.As
<byte, sbyte>(ref data
),
816 CorElementType
.ELEMENT_TYPE_U1
=> data
,
817 CorElementType
.ELEMENT_TYPE_BOOLEAN
=> Unsafe
.As
<byte, bool>(ref data
),
818 CorElementType
.ELEMENT_TYPE_I2
=> Unsafe
.As
<byte, short>(ref data
),
819 CorElementType
.ELEMENT_TYPE_U2
=> Unsafe
.As
<byte, ushort>(ref data
),
820 CorElementType
.ELEMENT_TYPE_CHAR
=> Unsafe
.As
<byte, char>(ref data
),
821 CorElementType
.ELEMENT_TYPE_I4
=> Unsafe
.As
<byte, int>(ref data
),
822 CorElementType
.ELEMENT_TYPE_U4
=> Unsafe
.As
<byte, uint>(ref data
),
823 CorElementType
.ELEMENT_TYPE_R4
=> Unsafe
.As
<byte, float>(ref data
),
824 CorElementType
.ELEMENT_TYPE_I8
=> Unsafe
.As
<byte, long>(ref data
),
825 CorElementType
.ELEMENT_TYPE_U8
=> Unsafe
.As
<byte, ulong>(ref data
),
826 CorElementType
.ELEMENT_TYPE_R8
=> Unsafe
.As
<byte, double>(ref data
),
827 CorElementType
.ELEMENT_TYPE_I
=> Unsafe
.As
<byte, IntPtr
>(ref data
),
828 CorElementType
.ELEMENT_TYPE_U
=> Unsafe
.As
<byte, UIntPtr
>(ref data
),
829 _
=> throw new InvalidOperationException(SR
.InvalidOperation_UnknownEnumType
),
833 private ulong ToUInt64()
835 ref byte data
= ref this.GetRawData();
836 switch (InternalGetCorElementType())
838 case CorElementType
.ELEMENT_TYPE_I1
:
839 return (ulong)Unsafe
.As
<byte, sbyte>(ref data
);
840 case CorElementType
.ELEMENT_TYPE_U1
:
842 case CorElementType
.ELEMENT_TYPE_BOOLEAN
:
843 return Convert
.ToUInt64(Unsafe
.As
<byte, bool>(ref data
), CultureInfo
.InvariantCulture
);
844 case CorElementType
.ELEMENT_TYPE_I2
:
845 return (ulong)Unsafe
.As
<byte, short>(ref data
);
846 case CorElementType
.ELEMENT_TYPE_U2
:
847 case CorElementType
.ELEMENT_TYPE_CHAR
:
848 return Unsafe
.As
<byte, ushort>(ref data
);
849 case CorElementType
.ELEMENT_TYPE_I4
:
850 return (ulong)Unsafe
.As
<byte, int>(ref data
);
851 case CorElementType
.ELEMENT_TYPE_U4
:
852 case CorElementType
.ELEMENT_TYPE_R4
:
853 return Unsafe
.As
<byte, uint>(ref data
);
854 case CorElementType
.ELEMENT_TYPE_I8
:
855 return (ulong)Unsafe
.As
<byte, long>(ref data
);
856 case CorElementType
.ELEMENT_TYPE_U8
:
857 case CorElementType
.ELEMENT_TYPE_R8
:
858 return Unsafe
.As
<byte, ulong>(ref data
);
859 case CorElementType
.ELEMENT_TYPE_I
:
860 return (ulong)Unsafe
.As
<byte, IntPtr
>(ref data
);
861 case CorElementType
.ELEMENT_TYPE_U
:
862 return (ulong)Unsafe
.As
<byte, UIntPtr
>(ref data
);
864 throw new InvalidOperationException(SR
.InvalidOperation_UnknownEnumType
);
870 #region Object Overrides
872 public override int GetHashCode()
874 // CONTRACT with the runtime: GetHashCode of enum types is implemented as GetHashCode of the underlying type.
875 // The runtime can bypass calls to Enum::GetHashCode and call the underlying type's GetHashCode directly
876 // to avoid boxing the enum.
877 ref byte data
= ref this.GetRawData();
878 return (InternalGetCorElementType()) switch
880 CorElementType
.ELEMENT_TYPE_I1
=> Unsafe
.As
<byte, sbyte>(ref data
).GetHashCode(),
881 CorElementType
.ELEMENT_TYPE_U1
=> data
.GetHashCode(),
882 CorElementType
.ELEMENT_TYPE_BOOLEAN
=> Unsafe
.As
<byte, bool>(ref data
).GetHashCode(),
883 CorElementType
.ELEMENT_TYPE_I2
=> Unsafe
.As
<byte, short>(ref data
).GetHashCode(),
884 CorElementType
.ELEMENT_TYPE_U2
=> Unsafe
.As
<byte, ushort>(ref data
).GetHashCode(),
885 CorElementType
.ELEMENT_TYPE_CHAR
=> Unsafe
.As
<byte, char>(ref data
).GetHashCode(),
886 CorElementType
.ELEMENT_TYPE_I4
=> Unsafe
.As
<byte, int>(ref data
).GetHashCode(),
887 CorElementType
.ELEMENT_TYPE_U4
=> Unsafe
.As
<byte, uint>(ref data
).GetHashCode(),
888 CorElementType
.ELEMENT_TYPE_R4
=> Unsafe
.As
<byte, float>(ref data
).GetHashCode(),
889 CorElementType
.ELEMENT_TYPE_I8
=> Unsafe
.As
<byte, long>(ref data
).GetHashCode(),
890 CorElementType
.ELEMENT_TYPE_U8
=> Unsafe
.As
<byte, ulong>(ref data
).GetHashCode(),
891 CorElementType
.ELEMENT_TYPE_R8
=> Unsafe
.As
<byte, double>(ref data
).GetHashCode(),
892 CorElementType
.ELEMENT_TYPE_I
=> Unsafe
.As
<byte, IntPtr
>(ref data
).GetHashCode(),
893 CorElementType
.ELEMENT_TYPE_U
=> Unsafe
.As
<byte, UIntPtr
>(ref data
).GetHashCode(),
894 _
=> throw new InvalidOperationException(SR
.InvalidOperation_UnknownEnumType
),
898 public override string ToString()
900 // Returns the value in a human readable format. For PASCAL style enums who's value maps directly the name of the field is returned.
901 // For PASCAL style enums who's values do not map directly the decimal value of the field is returned.
902 // For BitFlags (indicated by the Flags custom attribute): If for each bit that is set in the value there is a corresponding constant
903 // (a pure power of 2), then the OR string (ie "Red, Yellow") is returned. Otherwise, if the value is zero or if you can't create a string that consists of
904 // pure powers of 2 OR-ed together, you return a hex value
906 // Try to see if its one of the enum values, then we return a String back else the value
907 return InternalFormat((RuntimeType
)GetType(), ToUInt64()) ?? ValueToString();
912 [Obsolete("The provider argument is not used. Please use ToString(String).")]
913 public string ToString(string? format
, IFormatProvider
? provider
)
915 return ToString(format
);
919 #region Public Methods
920 public string ToString(string? format
)
922 if (string.IsNullOrEmpty(format
))
927 if (format
.Length
== 1)
937 return ValueToString();
941 return ValueToHexString();
945 return InternalFlagsFormat((RuntimeType
)GetType(), ToUInt64()) ?? ValueToString();
949 throw new FormatException(SR
.Format_InvalidEnumFormatSpecification
);
952 [Obsolete("The provider argument is not used. Please use ToString().")]
953 public string ToString(IFormatProvider
? provider
)
961 public TypeCode
GetTypeCode()
963 return (InternalGetCorElementType()) switch
965 CorElementType
.ELEMENT_TYPE_I1
=> TypeCode
.SByte
,
966 CorElementType
.ELEMENT_TYPE_U1
=> TypeCode
.Byte
,
967 CorElementType
.ELEMENT_TYPE_BOOLEAN
=> TypeCode
.Boolean
,
968 CorElementType
.ELEMENT_TYPE_I2
=> TypeCode
.Int16
,
969 CorElementType
.ELEMENT_TYPE_U2
=> TypeCode
.UInt16
,
970 CorElementType
.ELEMENT_TYPE_CHAR
=> TypeCode
.Char
,
971 CorElementType
.ELEMENT_TYPE_I4
=> TypeCode
.Int32
,
972 CorElementType
.ELEMENT_TYPE_U4
=> TypeCode
.UInt32
,
973 CorElementType
.ELEMENT_TYPE_I8
=> TypeCode
.Int64
,
974 CorElementType
.ELEMENT_TYPE_U8
=> TypeCode
.UInt64
,
975 _
=> throw new InvalidOperationException(SR
.InvalidOperation_UnknownEnumType
),
979 bool IConvertible
.ToBoolean(IFormatProvider
? provider
)
981 return Convert
.ToBoolean(GetValue(), CultureInfo
.CurrentCulture
);
984 char IConvertible
.ToChar(IFormatProvider
? provider
)
986 return Convert
.ToChar(GetValue(), CultureInfo
.CurrentCulture
);
989 sbyte IConvertible
.ToSByte(IFormatProvider
? provider
)
991 return Convert
.ToSByte(GetValue(), CultureInfo
.CurrentCulture
);
994 byte IConvertible
.ToByte(IFormatProvider
? provider
)
996 return Convert
.ToByte(GetValue(), CultureInfo
.CurrentCulture
);
999 short IConvertible
.ToInt16(IFormatProvider
? provider
)
1001 return Convert
.ToInt16(GetValue(), CultureInfo
.CurrentCulture
);
1004 ushort IConvertible
.ToUInt16(IFormatProvider
? provider
)
1006 return Convert
.ToUInt16(GetValue(), CultureInfo
.CurrentCulture
);
1009 int IConvertible
.ToInt32(IFormatProvider
? provider
)
1011 return Convert
.ToInt32(GetValue(), CultureInfo
.CurrentCulture
);
1014 uint IConvertible
.ToUInt32(IFormatProvider
? provider
)
1016 return Convert
.ToUInt32(GetValue(), CultureInfo
.CurrentCulture
);
1019 long IConvertible
.ToInt64(IFormatProvider
? provider
)
1021 return Convert
.ToInt64(GetValue(), CultureInfo
.CurrentCulture
);
1024 ulong IConvertible
.ToUInt64(IFormatProvider
? provider
)
1026 return Convert
.ToUInt64(GetValue(), CultureInfo
.CurrentCulture
);
1029 float IConvertible
.ToSingle(IFormatProvider
? provider
)
1031 return Convert
.ToSingle(GetValue(), CultureInfo
.CurrentCulture
);
1034 double IConvertible
.ToDouble(IFormatProvider
? provider
)
1036 return Convert
.ToDouble(GetValue(), CultureInfo
.CurrentCulture
);
1039 decimal IConvertible
.ToDecimal(IFormatProvider
? provider
)
1041 return Convert
.ToDecimal(GetValue(), CultureInfo
.CurrentCulture
);
1044 DateTime IConvertible
.ToDateTime(IFormatProvider
? provider
)
1046 throw new InvalidCastException(SR
.Format(SR
.InvalidCast_FromTo
, "Enum", "DateTime"));
1049 object IConvertible
.ToType(Type type
, IFormatProvider
? provider
)
1051 return Convert
.DefaultToType((IConvertible
)this, type
, provider
);
1056 [CLSCompliant(false)]
1057 public static object ToObject(Type enumType
, sbyte value) =>
1058 InternalBoxEnum(ValidateRuntimeType(enumType
), value);
1060 public static object ToObject(Type enumType
, short value) =>
1061 InternalBoxEnum(ValidateRuntimeType(enumType
), value);
1063 public static object ToObject(Type enumType
, int value) =>
1064 InternalBoxEnum(ValidateRuntimeType(enumType
), value);
1066 public static object ToObject(Type enumType
, byte value) =>
1067 InternalBoxEnum(ValidateRuntimeType(enumType
), value);
1069 [CLSCompliant(false)]
1070 public static object ToObject(Type enumType
, ushort value) =>
1071 InternalBoxEnum(ValidateRuntimeType(enumType
), value);
1073 [CLSCompliant(false)]
1074 public static object ToObject(Type enumType
, uint value) =>
1075 InternalBoxEnum(ValidateRuntimeType(enumType
), value);
1077 public static object ToObject(Type enumType
, long value) =>
1078 InternalBoxEnum(ValidateRuntimeType(enumType
), value);
1080 [CLSCompliant(false)]
1081 public static object ToObject(Type enumType
, ulong value) =>
1082 InternalBoxEnum(ValidateRuntimeType(enumType
), unchecked((long)value));
1084 private static object ToObject(Type enumType
, char value) =>
1085 InternalBoxEnum(ValidateRuntimeType(enumType
), value);
1087 private static object ToObject(Type enumType
, bool value) =>
1088 InternalBoxEnum(ValidateRuntimeType(enumType
), value ? 1 : 0);