Fix pragma warning restore (dotnet/coreclr#26389)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Enum.cs
blob2444e9ec2c655f5dc38240fdc21ff51b0d6bb31a
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;
12 #if CORERT
13 using CorElementType = System.Runtime.RuntimeImports.RhCorElementType;
14 using RuntimeType = System.Type;
15 using EnumInfo = Internal.Runtime.Augments.EnumInfo;
16 #endif
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#.
30 namespace System
32 [Serializable]
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 = ',';
38 #endregion
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);
85 default:
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);
116 if (index >= 0)
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 ?
157 names[0] :
158 "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
169 // name string.
170 int index = values.Length - 1;
171 while (index >= 0)
173 if (values[index] == resultValue)
175 return names[index];
178 if (values[index] < resultValue)
180 break;
183 index--;
186 // Now look for multiple matches, storing the indices of the values
187 // into our span.
188 int resultLength = 0, foundItemsCount = 0;
189 while (index >= 0)
191 ulong currentValue = values[index];
192 if (index == 0 && currentValue == 0)
194 break;
197 if ((resultValue & currentValue) == currentValue)
199 resultValue -= currentValue;
200 foundItems[foundItemsCount++] = index;
201 resultLength = checked(resultLength + names[index].Length);
204 index--;
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)
213 return null;
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;
229 resultSpan[1] = ' ';
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);
238 return result;
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),
260 return result;
263 #endregion
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);
273 return result!;
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);
283 return result;
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)
301 if (throwOnFailure)
303 throw value == null ?
304 new ArgumentNullException(nameof(value)) :
305 new ArgumentException(SR.Arg_MustContainEnumInfo, nameof(value));
307 result = null;
308 return false;
311 int intResult;
312 uint uintResult;
313 bool parsed;
315 switch (Type.GetTypeCode(rt))
317 case TypeCode.SByte:
318 parsed = TryParseInt32Enum(rt, value, valueSpan, sbyte.MinValue, sbyte.MaxValue, ignoreCase, throwOnFailure, TypeCode.SByte, out intResult);
319 result = parsed ? InternalBoxEnum(rt, intResult) : null;
320 return parsed;
322 case TypeCode.Int16:
323 parsed = TryParseInt32Enum(rt, value, valueSpan, short.MinValue, short.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int16, out intResult);
324 result = parsed ? InternalBoxEnum(rt, intResult) : null;
325 return parsed;
327 case TypeCode.Int32:
328 parsed = TryParseInt32Enum(rt, value, valueSpan, int.MinValue, int.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int32, out intResult);
329 result = parsed ? InternalBoxEnum(rt, intResult) : null;
330 return parsed;
332 case TypeCode.Byte:
333 parsed = TryParseUInt32Enum(rt, value, valueSpan, byte.MaxValue, ignoreCase, throwOnFailure, TypeCode.Byte, out uintResult);
334 result = parsed ? InternalBoxEnum(rt, uintResult) : null;
335 return parsed;
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;
340 return parsed;
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;
345 return parsed;
347 case TypeCode.Int64:
348 parsed = TryParseInt64Enum(rt, value, valueSpan, ignoreCase, throwOnFailure, out long longResult);
349 result = parsed ? InternalBoxEnum(rt, longResult) : null;
350 return parsed;
352 case TypeCode.UInt64:
353 parsed = TryParseUInt64Enum(rt, value, valueSpan, ignoreCase, throwOnFailure, out ulong ulongResult);
354 result = parsed ? InternalBoxEnum(rt, (long)ulongResult) : null;
355 return parsed;
357 default:
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)
380 if (throwOnFailure)
382 throw value == null ?
383 new ArgumentNullException(nameof(value)) :
384 new ArgumentException(SR.Arg_MustContainEnumInfo, nameof(value));
386 result = default;
387 return false;
390 int intResult;
391 uint uintResult;
392 bool parsed;
393 RuntimeType rt = (RuntimeType)typeof(TEnum);
395 switch (Type.GetTypeCode(typeof(TEnum)))
397 case TypeCode.SByte:
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);
401 return parsed;
403 case TypeCode.Int16:
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);
407 return parsed;
409 case TypeCode.Int32:
410 parsed = TryParseInt32Enum(rt, value, valueSpan, int.MinValue, int.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int32, out intResult);
411 result = Unsafe.As<int, TEnum>(ref intResult);
412 return parsed;
414 case TypeCode.Byte:
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);
418 return parsed;
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);
424 return parsed;
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);
429 return parsed;
431 case TypeCode.Int64:
432 parsed = TryParseInt64Enum(rt, value, valueSpan, ignoreCase, throwOnFailure, out long longResult);
433 result = Unsafe.As<long, TEnum>(ref longResult);
434 return parsed;
436 case TypeCode.UInt64:
437 parsed = TryParseUInt64Enum(rt, value, valueSpan, ignoreCase, throwOnFailure, out ulong ulongResult);
438 result = Unsafe.As<ulong, TEnum>(ref ulongResult);
439 return parsed;
441 default:
442 parsed = TryParseRareEnum(rt, value, valueSpan, ignoreCase, throwOnFailure, out object? objectResult);
443 result = parsed ? (TEnum)objectResult! : default;
444 return parsed;
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)
452 Debug.Assert(
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))
465 return true;
468 status = Number.ParsingStatus.Overflow;
472 if (status == Number.ParsingStatus.Overflow)
474 if (throwOnFailure)
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);
483 return true;
486 result = 0;
487 return false;
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)
493 Debug.Assert(
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)
506 return true;
509 status = Number.ParsingStatus.Overflow;
513 if (status == Number.ParsingStatus.Overflow)
515 if (throwOnFailure)
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);
524 return true;
527 result = 0;
528 return false;
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)
542 return true;
546 if (status == Number.ParsingStatus.Overflow)
548 if (throwOnFailure)
550 Number.ThrowOverflowException(TypeCode.Int64);
553 else if (TryParseByName(enumType, originalValueString, value, ignoreCase, throwOnFailure, out ulong ulongResult))
555 result = (long)ulongResult;
556 return true;
559 result = 0;
560 return false;
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)
574 return true;
578 if (status == Number.ParsingStatus.Overflow)
580 if (throwOnFailure)
582 Number.ThrowOverflowException(TypeCode.UInt64);
585 else if (TryParseByName(enumType, originalValueString, value, ignoreCase, throwOnFailure, out result))
587 return true;
590 result = 0;
591 return false;
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)
597 Debug.Assert(
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)!);
614 return true;
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)
623 result = null;
624 return false;
628 if (TryParseByName(enumType, originalValueString, value, ignoreCase, throwOnFailure, out ulong ulongResult))
632 result = ToObject(enumType, ulongResult);
633 return true;
635 catch when (!throwOnFailure) { }
638 result = null;
639 return false;
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;
649 bool parsed = true;
650 ulong localResult = 0;
651 while (value.Length > 0)
653 // Find the next separator.
654 ReadOnlySpan<char> subvalue;
655 int endIndex = value.IndexOf(EnumSeparatorChar);
656 if (endIndex == -1)
658 // No next separator; use the remainder as the next value.
659 subvalue = value.Trim();
660 value = default;
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);
668 else
670 // Last char was a separator, which is invalid.
671 parsed = false;
672 break;
675 // Try to match this substring against each enum name
676 bool success = false;
677 if (ignoreCase)
679 for (int i = 0; i < enumNames.Length; i++)
681 if (subvalue.EqualsOrdinalIgnoreCase(enumNames[i]))
683 localResult |= enumValues[i];
684 success = true;
685 break;
689 else
691 for (int i = 0; i < enumNames.Length; i++)
693 if (subvalue.EqualsOrdinal(enumNames[i]))
695 localResult |= enumValues[i];
696 success = true;
697 break;
702 if (!success)
704 parsed = false;
705 break;
709 if (parsed)
711 result = localResult;
712 return true;
715 if (throwOnFailure)
717 throw new ArgumentException(SR.Format(SR.Arg_EnumValueNotFound, originalValueString));
720 result = 0;
721 return false;
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)
729 if (value == null)
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);
755 if (value == null)
756 throw new ArgumentNullException(nameof(value));
758 if (format == null)
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)
785 switch (format[0])
787 case 'G':
788 case 'g':
789 return GetEnumName(rtType, ToUInt64(value)) ?? value.ToString()!;
791 case 'D':
792 case 'd':
793 return value.ToString()!;
795 case 'X':
796 case 'x':
797 return ValueToHexString(value);
799 case 'F':
800 case 'f':
801 return InternalFlagsFormat(rtType, ToUInt64(value)) ?? value.ToString()!;
805 throw new FormatException(SR.Format_InvalidEnumFormatSpecification);
807 #endregion
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:
841 return data;
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);
863 default:
864 throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType);
868 #endregion
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();
909 #endregion
911 #region IFormattable
912 [Obsolete("The provider argument is not used. Please use ToString(String).")]
913 public string ToString(string? format, IFormatProvider? provider)
915 return ToString(format);
917 #endregion
919 #region Public Methods
920 public string ToString(string? format)
922 if (string.IsNullOrEmpty(format))
924 return ToString();
927 if (format.Length == 1)
929 switch (format[0])
931 case 'G':
932 case 'g':
933 return ToString();
935 case 'D':
936 case 'd':
937 return ValueToString();
939 case 'X':
940 case 'x':
941 return ValueToHexString();
943 case 'F':
944 case 'f':
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)
955 return ToString();
958 #endregion
960 #region IConvertible
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);
1053 #endregion
1055 #region ToObject
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);
1090 #endregion