// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Rare enums types not expressible in C# are not supported in native AOT #if !NATIVEAOT #define RARE_ENUMS #endif using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace System { /// <summary>Provides the base class for enumerations.</summary> [Serializable] [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] public abstract partial class Enum : ValueType, IComparable, ISpanFormattable, IConvertible { // Notes: // - This implementation includes partial support for float/double/nint/nuint-based enums. // The type loader does not prohibit such enums, and older versions of the ECMA spec include // them as possible enum types. However there are many things broken throughout the stack for // float/double/nint/nuint enums. There was a conscious decision made to not fix the whole stack // to work well for them because the right behavior is often unclear, and it is hard to test and // very low value because such enums cannot be expressed in C# and are very rarely encountered. // - To avoid significant code bloat for public generic APIs, we avoid making the whole implementation // generic on the enum type (TEnum) all the way through the implementation. Instead, at the public // entry point we look up the underlying type for that enum, and use that underlying type as the // generic for the rest of that call tree (TUnderlying). That type is important for controlling // how the parsing and formatting APIs behave, in particular when parsing and formatting an enum // value as a number. However, various APIs also need to be able to look up names associated with // values, and those operations assume the values in the underlying storage are sorted, and for // integer underlying types, sorted based on the unsigned representation of the underlying value. // So, for those operations we also or instead pass along a generic for the actual type in which // the data is stored in the underlying values array (TStorage), which will either be the same // as the underlying type if the underlying type is not a signed integer, or will be the unsigned // counterpart to the underlying type if the underlying type is a signed integer. This also has // the benefit of further limited code bloat, as for the underlying types representable in C#, // we then only need up to 4 instantiations of some code paths rather than 8. /// <summary>Character used to separate flag enum values when formatted in a list.</summary> private const char EnumSeparatorChar = ','; /// <summary>Retrieves the name of the constant in the specified enumeration type that has the specified value.</summary> /// <typeparam name="TEnum">The type of the enumeration.</typeparam> /// <param name="value">The value of a particular enumerated constant in terms of its underlying type.</param> /// <returns> /// A string containing the name of the enumerated constant in <typeparamref name="TEnum"/> whose value is <paramref name="value"/>, /// or <see langword="null"/> if no such constant is found. /// </returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe string? GetName<TEnum>(TEnum value) where TEnum : struct, Enum { RuntimeType rt = (RuntimeType)typeof(TEnum); Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); if (underlyingType == typeof(sbyte) || underlyingType == typeof(byte)) return GetNameInlined(GetEnumInfo<byte>(rt), *(byte*)&value); if (underlyingType == typeof(short) || underlyingType == typeof(ushort)) return GetNameInlined(GetEnumInfo<ushort>(rt), *(ushort*)&value); if (underlyingType == typeof(int) || underlyingType == typeof(uint)) return GetNameInlined(GetEnumInfo<uint>(rt), *(uint*)&value); if (underlyingType == typeof(long) || underlyingType == typeof(ulong)) return GetNameInlined(GetEnumInfo<ulong>(rt), *(ulong*)&value); #if RARE_ENUMS if (underlyingType == typeof(nint) || underlyingType == typeof(nuint)) return GetNameInlined(GetEnumInfo<nuint>(rt), *(nuint*)&value); if (underlyingType == typeof(float)) return GetNameInlined(GetEnumInfo<float>(rt), *(float*)&value); if (underlyingType == typeof(double)) return GetNameInlined(GetEnumInfo<double>(rt), *(double*)&value); if (underlyingType == typeof(char)) return GetNameInlined(GetEnumInfo<char>(rt), *(char*)&value); #endif throw CreateUnknownEnumTypeException(); } /// <summary>Retrieves the name of the constant in the specified enumeration type that has the specified value.</summary> /// <param name="enumType">An enumeration type.</param> /// <param name="value">The value of a particular enumerated constant in terms of its underlying type.</param> /// <returns> /// A string containing the name of the enumerated constant in <paramref name="enumType"/> whose value is <paramref name="value"/>, /// or <see langword="null"/> if no such constant is found. /// </returns> /// <exception cref="ArgumentNullException"><paramref name="enumType"/> or <paramref name="value"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException"> /// <paramref name="enumType"/> is not an <see cref="Enum"/>, or <paramref name="value"/> is neither of type <paramref name="enumType"/> /// nor does it have the same underlying type as <paramref name="enumType"/>. /// </exception> public static string? GetName(Type enumType, object value) { ArgumentNullException.ThrowIfNull(enumType); return enumType.GetEnumName(value); } /// <summary>Retrieves the name of the constant in the specified enumeration type that has the specified value.</summary> /// <param name="enumType">An enumeration type.</param> /// <param name="uint64Value">The value of a particular enumerated constant in terms of its underlying type, cast to a <see cref="ulong"/>.</param> /// <returns> /// A string containing the name of the enumerated constant in <paramref name="enumType"/> whose value is <paramref name="uint64Value"/>, /// or <see langword="null"/> if no such constant is found. /// </returns> internal static string? GetName(RuntimeType enumType, ulong uint64Value) { // For a given underlying type, validate that the specified ulong is in the range // of that type. If it's not, it definitely doesn't match. If it is, delegate // to GetName<TUnderlyingType> to look it up. Type underlyingType = enumType.GetEnumUnderlyingType(); switch (Type.GetTypeCode(underlyingType)) // can't use InternalGetCorElementType as enumType may actually be the underlying type { case TypeCode.SByte: if ((long)uint64Value < sbyte.MinValue || (long)uint64Value > sbyte.MaxValue) return null; return GetName(GetEnumInfo<byte>(enumType), (byte)(sbyte)uint64Value); case TypeCode.Byte: if (uint64Value > byte.MaxValue) return null; return GetName(GetEnumInfo<byte>(enumType), (byte)uint64Value); case TypeCode.Int16: if ((long)uint64Value < short.MinValue || (long)uint64Value > short.MaxValue) return null; return GetName(GetEnumInfo<ushort>(enumType), (ushort)(short)uint64Value); case TypeCode.UInt16: if (uint64Value > ushort.MaxValue) return null; return GetName(GetEnumInfo<ushort>(enumType), (ushort)uint64Value); case TypeCode.Int32: if ((long)uint64Value < int.MinValue || (long)uint64Value > int.MaxValue) return null; return GetName(GetEnumInfo<uint>(enumType), (uint)(int)uint64Value); case TypeCode.UInt32: if (uint64Value > uint.MaxValue) return null; return GetName(GetEnumInfo<uint>(enumType), (uint)uint64Value); case TypeCode.Int64: return GetName(GetEnumInfo<ulong>(enumType), (ulong)(long)uint64Value); case TypeCode.UInt64: return GetName(GetEnumInfo<ulong>(enumType), uint64Value); #if RARE_ENUMS case TypeCode.Char: if (uint64Value > char.MaxValue) return null; return GetName(GetEnumInfo<char>(enumType), (char)uint64Value); #endif }; #if RARE_ENUMS if (underlyingType == typeof(nint)) { if ((long)uint64Value < nint.MinValue || (long)uint64Value > nint.MaxValue) return null; return GetName(GetEnumInfo<nuint>(enumType), (nuint)(nint)uint64Value); } if (underlyingType == typeof(nuint)) { if (uint64Value > nuint.MaxValue) return null; return GetName(GetEnumInfo<nuint>(enumType), (nuint)uint64Value); } #endif throw CreateUnknownEnumTypeException(); } [MethodImpl(MethodImplOptions.NoInlining)] private static string? GetName<TStorage>(EnumInfo<TStorage> enumInfo, TStorage value) where TStorage : struct, INumber<TStorage> => GetNameInlined(enumInfo, value); /// <summary>Look up the name for the specified underlying value using the cached <see cref="EnumInfo{TStorage}"/> for the associated enum.</summary> /// <typeparam name="TStorage">The type stored in the EnumInfo. This will either be the same as the underlying type for the enum or its unsigned counterpart if the underlying type is a signed integer.</typeparam> /// <param name="enumInfo">The cached <see cref="EnumInfo{TStorage}"/> for the enum type.</param> /// <param name="value">The underlying value for which we're searching.</param> /// <returns>The name of the value if found; otherwise, <see langword="null"/>.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe string? GetNameInlined<TStorage>(EnumInfo<TStorage> enumInfo, TStorage value) where TStorage : struct, INumber<TStorage> { string[] names = enumInfo.Names; // If the values are known to be sequential starting from 0, then we can simply compare the value // against the length of the array. The value matches iff it's in-bounds, and if it is, the value // in the array is where the corresponding name is stored. if (enumInfo.ValuesAreSequentialFromZero) { if (sizeof(TStorage) <= sizeof(uint)) { // Special-case types types that are <= sizeof(int), as we can then eliminate a bounds check on the array. uint uint32Value = uint.CreateTruncating(value); if (uint32Value < (uint)names.Length) { return names[uint32Value]; } } else { // Handle the remaining types. if (ulong.CreateTruncating(value) < (ulong)names.Length) { return names[uint.CreateTruncating(value)]; } } } else { // Search for the value in the array of values. If we find a non-negative index, // that's the location of the corresponding name in the names array. int index = FindDefinedIndex(enumInfo.Values, value); if ((uint)index < (uint)names.Length) { return names[index]; } } // Return null so the caller knows no individual named value could be found. return null; } /// <summary>Retrieves an array of the names of the constants in a specified enumeration type.</summary> /// <typeparam name="TEnum">The type of the enumeration.</typeparam> /// <returns>A string array of the names of the constants in <typeparamref name="TEnum"/>.</returns> public static string[] GetNames<TEnum>() where TEnum : struct, Enum { string[] names; RuntimeType rt = (RuntimeType)typeof(TEnum); Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); // Get the cached names array. if (underlyingType == typeof(sbyte) || underlyingType == typeof(byte)) names = GetEnumInfo<byte>(rt).Names; else if (underlyingType == typeof(short) || underlyingType == typeof(ushort)) names = GetEnumInfo<ushort>(rt).Names; else if (underlyingType == typeof(int) || underlyingType == typeof(uint)) names = GetEnumInfo<uint>(rt).Names; else if (underlyingType == typeof(long) || underlyingType == typeof(ulong)) names = GetEnumInfo<ulong>(rt).Names; #if RARE_ENUMS else if (underlyingType == typeof(nint) || underlyingType == typeof(nuint)) names = GetEnumInfo<nuint>(rt).Names; else if (underlyingType == typeof(float)) names = GetEnumInfo<float>(rt).Names; else if (underlyingType == typeof(double)) names = GetEnumInfo<double>(rt).Names; else if (underlyingType == typeof(char)) names = GetEnumInfo<char>(rt).Names; #endif else throw CreateUnknownEnumTypeException(); // Return a clone of the array to avoid exposing the cached array instance. return new ReadOnlySpan<string>(names).ToArray(); } /// <summary>Retrieves an array of the names of the constants in a specified enumeration.</summary> /// <param name="enumType">An enumeration type.</param> /// <returns>A string array of the names of the constants in <paramref name="enumType"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="enumType"/> is null.</exception> /// <exception cref="ArgumentException"><paramref name="enumType"/> is not an <see cref="Enum"/>.</exception> public static string[] GetNames(Type enumType) { ArgumentNullException.ThrowIfNull(enumType); return enumType.GetEnumNames(); } /// <summary>Gets the cached names array for the specified enum type, without making a copy.</summary> /// <remarks>The returned array should not be exposed outside of this assembly.</remarks> internal static string[] GetNamesNoCopy(RuntimeType enumType) { Debug.Assert(enumType.IsActualEnum); return InternalGetCorElementType(enumType) switch { CorElementType.ELEMENT_TYPE_I1 or CorElementType.ELEMENT_TYPE_U1 => GetEnumInfo<byte>(enumType).Names, CorElementType.ELEMENT_TYPE_I2 or CorElementType.ELEMENT_TYPE_U2 => GetEnumInfo<ushort>(enumType).Names, CorElementType.ELEMENT_TYPE_I4 or CorElementType.ELEMENT_TYPE_U4 => GetEnumInfo<uint>(enumType).Names, CorElementType.ELEMENT_TYPE_I8 or CorElementType.ELEMENT_TYPE_U8 => GetEnumInfo<ulong>(enumType).Names, #if RARE_ENUMS CorElementType.ELEMENT_TYPE_I or CorElementType.ELEMENT_TYPE_U => GetEnumInfo<nuint>(enumType).Names, CorElementType.ELEMENT_TYPE_R4 => GetEnumInfo<float>(enumType).Names, CorElementType.ELEMENT_TYPE_R8 => GetEnumInfo<double>(enumType).Names, CorElementType.ELEMENT_TYPE_CHAR => GetEnumInfo<char>(enumType).Names, #endif _ => throw CreateUnknownEnumTypeException(), }; } /// <summary>Returns the underlying type of the specified enumeration.</summary> /// <param name="enumType">The enumeration whose underlying type will be retrieved.</param> /// <returns>The underlying type of <paramref name="enumType"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="enumType"/> is null.</exception> /// <exception cref="ArgumentException"><paramref name="enumType"/> is not an <see cref="Enum"/>.</exception> public static Type GetUnderlyingType(Type enumType) { ArgumentNullException.ThrowIfNull(enumType); return enumType.GetEnumUnderlyingType(); } /// <summary>Retrieves an array of the values of the constants in a specified enumeration type.</summary> /// <typeparam name="TEnum">The type of the enumeration.</typeparam> /// <returns>An array that contains the values of the constants in <typeparamref name="TEnum"/>.</returns> public static TEnum[] GetValues<TEnum>() where TEnum : struct, Enum { Array values = GetValuesAsUnderlyingTypeNoCopy((RuntimeType)typeof(TEnum)); var result = new TEnum[values.Length]; Array.Copy(values, result, values.Length); return result; } /// <summary>Retrieves an array of the values of the constants in a specified enumeration.</summary> /// <param name="enumType">An enumeration type.</param> /// <returns>An array that contains the values of the constants in <paramref name="enumType"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="enumType"/> is null.</exception> /// <exception cref="ArgumentException"><paramref name="enumType"/> is not an <see cref="Enum"/>.</exception> /// <exception cref="InvalidOperationException"> /// The method is invoked by reflection in a reflection-only context, or <paramref name="enumType"/> is a type from an assembly loaded in a reflection-only context. /// </exception> [RequiresDynamicCode("It might not be possible to create an array of the enum type at runtime. Use the GetValues<TEnum> overload or the GetValuesAsUnderlyingType method instead.")] public static Array GetValues(Type enumType) { ArgumentNullException.ThrowIfNull(enumType); return enumType.GetEnumValues(); } /// <summary>Retrieves an array of the values of the underlying type constants in a specified enumeration type.</summary> /// <typeparam name="TEnum">An enumeration type.</typeparam> /// <remarks> /// You can use this method to get enumeration values when it's hard to create an array of the enumeration type. /// For example, you might use this method for the <see cref="T:System.Reflection.MetadataLoadContext" /> enumeration or on a platform where run-time code generation is not available. /// </remarks> /// <returns>An array that contains the values of the underlying type constants in <typeparamref name="TEnum" />.</returns> public static Array GetValuesAsUnderlyingType<TEnum>() where TEnum : struct, Enum => typeof(TEnum).GetEnumValuesAsUnderlyingType(); /// <summary>Retrieves an array of the values of the underlying type constants in a specified enumeration.</summary> /// <param name="enumType">An enumeration type.</param> /// <remarks> /// You can use this method to get enumeration values when it's hard to create an array of the enumeration type. /// For example, you might use this method for the MetadataLoadContext enumeration or on a platform where run-time code generation is not available. /// </remarks> /// <returns>An array that contains the values of the underlying type constants in <paramref name="enumType" />.</returns> /// <exception cref="ArgumentNullException"><paramref name="enumType" /> is null.</exception> /// <exception cref="ArgumentException"><paramref name="enumType" /> is not an enumeration type.</exception> public static Array GetValuesAsUnderlyingType(Type enumType) { ArgumentNullException.ThrowIfNull(enumType); return enumType.GetEnumValuesAsUnderlyingType(); } /// <summary>Retrieves an array of the values of the underlying type constants in a specified enumeration type.</summary> internal static Array GetValuesAsUnderlyingType(RuntimeType enumType) { Debug.Assert(enumType.IsActualEnum); return InternalGetCorElementType(enumType) switch { CorElementType.ELEMENT_TYPE_I1 => GetEnumInfo<byte>(enumType, getNames: false).CloneValues<sbyte>(), CorElementType.ELEMENT_TYPE_U1 => GetEnumInfo<byte>(enumType, getNames: false).CloneValues<byte>(), CorElementType.ELEMENT_TYPE_I2 => GetEnumInfo<ushort>(enumType, getNames: false).CloneValues<short>(), CorElementType.ELEMENT_TYPE_U2 => GetEnumInfo<ushort>(enumType, getNames: false).CloneValues<ushort>(), CorElementType.ELEMENT_TYPE_I4 => GetEnumInfo<uint>(enumType, getNames: false).CloneValues<int>(), CorElementType.ELEMENT_TYPE_U4 => GetEnumInfo<uint>(enumType, getNames: false).CloneValues<uint>(), CorElementType.ELEMENT_TYPE_I8 => GetEnumInfo<ulong>(enumType, getNames: false).CloneValues<long>(), CorElementType.ELEMENT_TYPE_U8 => GetEnumInfo<ulong>(enumType, getNames: false).CloneValues<ulong>(), #if RARE_ENUMS CorElementType.ELEMENT_TYPE_I => GetEnumInfo<nuint>(enumType, getNames: false).CloneValues<nint>(), CorElementType.ELEMENT_TYPE_U => GetEnumInfo<nuint>(enumType, getNames: false).CloneValues<nuint>(), CorElementType.ELEMENT_TYPE_R4 => GetEnumInfo<float>(enumType, getNames: false).CloneValues<float>(), CorElementType.ELEMENT_TYPE_R8 => GetEnumInfo<double>(enumType, getNames: false).CloneValues<double>(), CorElementType.ELEMENT_TYPE_CHAR => GetEnumInfo<char>(enumType, getNames: false).CloneValues<char>(), #endif _ => throw CreateUnknownEnumTypeException(), }; } /// <summary>Retrieves the cached array of the values of the underlying type constants in a specified enumeration.</summary> /// <remarks> /// The returned array should not be exposed outside of this assembly. /// It also may be using the corresponding unsigned type if the actual underlying type was signed. /// </remarks> internal static Array GetValuesAsUnderlyingTypeNoCopy(RuntimeType enumType) { Debug.Assert(enumType.IsActualEnum); return InternalGetCorElementType(enumType) switch { CorElementType.ELEMENT_TYPE_I1 => GetEnumInfo<byte>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_U1 => GetEnumInfo<byte>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_I2 => GetEnumInfo<ushort>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_U2 => GetEnumInfo<ushort>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_I4 => GetEnumInfo<uint>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_U4 => GetEnumInfo<uint>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_I8 => GetEnumInfo<ulong>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_U8 => GetEnumInfo<ulong>(enumType, getNames: false).Values, #if RARE_ENUMS CorElementType.ELEMENT_TYPE_I => GetEnumInfo<nuint>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_U => GetEnumInfo<nuint>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_R4 => GetEnumInfo<float>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_R8 => GetEnumInfo<double>(enumType, getNames: false).Values, CorElementType.ELEMENT_TYPE_CHAR => GetEnumInfo<char>(enumType, getNames: false).Values, #endif _ => throw CreateUnknownEnumTypeException(), }; } /// <summary>Determines whether one or more bit fields are set in the current instance.</summary> /// <param name="flag">An enumeration value.</param> /// <returns><see langword="true"/> if the bit field or bit fields that are set in flag are also set in the current instance; otherwise, <see langword="false"/>.</returns> /// <exception cref="ArgumentException"><paramref name="flag"/> is a different type than the current instance.</exception> [Intrinsic] public bool HasFlag(Enum flag) { ArgumentNullException.ThrowIfNull(flag); if (GetType() != flag.GetType() && !GetType().IsEquivalentTo(flag.GetType())) throw new ArgumentException(SR.Format(SR.Argument_EnumTypeDoesNotMatch, flag.GetType(), GetType())); ref byte pThisValue = ref this.GetRawData(); ref byte pFlagsValue = ref flag.GetRawData(); switch (InternalGetCorElementType()) { case CorElementType.ELEMENT_TYPE_I1: case CorElementType.ELEMENT_TYPE_U1: { byte flagsValue = pFlagsValue; return (pThisValue & flagsValue) == flagsValue; } case CorElementType.ELEMENT_TYPE_I2: case CorElementType.ELEMENT_TYPE_U2: { ushort flagsValue = Unsafe.As<byte, ushort>(ref pFlagsValue); return (Unsafe.As<byte, ushort>(ref pThisValue) & flagsValue) == flagsValue; } case CorElementType.ELEMENT_TYPE_I4: case CorElementType.ELEMENT_TYPE_U4: { uint flagsValue = Unsafe.As<byte, uint>(ref pFlagsValue); return (Unsafe.As<byte, uint>(ref pThisValue) & flagsValue) == flagsValue; } case CorElementType.ELEMENT_TYPE_I8: case CorElementType.ELEMENT_TYPE_U8: { ulong flagsValue = Unsafe.As<byte, ulong>(ref pFlagsValue); return (Unsafe.As<byte, ulong>(ref pThisValue) & flagsValue) == flagsValue; } #if RARE_ENUMS case CorElementType.ELEMENT_TYPE_BOOLEAN: goto case CorElementType.ELEMENT_TYPE_U1; case CorElementType.ELEMENT_TYPE_CHAR: goto case CorElementType.ELEMENT_TYPE_U2; case CorElementType.ELEMENT_TYPE_R4: goto case CorElementType.ELEMENT_TYPE_U4; case CorElementType.ELEMENT_TYPE_R8: goto case CorElementType.ELEMENT_TYPE_U8; case CorElementType.ELEMENT_TYPE_I: case CorElementType.ELEMENT_TYPE_U: #if TARGET_32BIT goto case CorElementType.ELEMENT_TYPE_U4; #else goto case CorElementType.ELEMENT_TYPE_U8; #endif #endif default: Debug.Fail("Unknown enum underlying type"); return false; } } /// <summary>Returns a <see cref="bool"/> telling whether a given integral value exists in a specified enumeration.</summary> /// <typeparam name="TEnum">The type of the enumeration.</typeparam> /// <param name="value">The value in <typeparamref name="TEnum"/>.</param> /// <returns><see langword="true"/> if a given integral value exists in a specified enumeration; <see langword="false"/>, otherwise.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool IsDefined<TEnum>(TEnum value) where TEnum : struct, Enum { RuntimeType rt = (RuntimeType)typeof(TEnum); Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); if (underlyingType == typeof(sbyte) || underlyingType == typeof(byte)) return IsDefinedPrimitive(rt, *(byte*)&value); if (underlyingType == typeof(short) || underlyingType == typeof(ushort)) return IsDefinedPrimitive(rt, *(ushort*)&value); if (underlyingType == typeof(int) || underlyingType == typeof(uint)) return IsDefinedPrimitive(rt, *(uint*)&value); if (underlyingType == typeof(long) || underlyingType == typeof(ulong)) return IsDefinedPrimitive(rt, *(ulong*)&value); #if RARE_ENUMS if (underlyingType == typeof(nint) || underlyingType == typeof(nuint)) return IsDefinedPrimitive(rt, *(nuint*)&value); if (underlyingType == typeof(float)) return IsDefinedPrimitive(rt, *(float*)&value); if (underlyingType == typeof(double)) return IsDefinedPrimitive(rt, *(double*)&value); if (underlyingType == typeof(char)) return IsDefinedPrimitive(rt, *(char*)&value); #endif throw CreateUnknownEnumTypeException(); } /// <summary>Gets whether the specified individual value is defined in the specified enum.</summary> internal static bool IsDefinedPrimitive<TStorage>(RuntimeType enumType, TStorage value) where TStorage : struct, INumber<TStorage> { EnumInfo<TStorage> enumInfo = GetEnumInfo<TStorage>(enumType, getNames: false); TStorage[] values = enumInfo.Values; // If the enum's values are all sequentially numbered starting from 0, then we can // just return if the requested index is in range. if (enumInfo.ValuesAreSequentialFromZero) { return ulong.CreateTruncating(value) < (ulong)values.Length; } // Otherwise, search for the value. return FindDefinedIndex(values, value) >= 0; } /// <summary>Returns a <see cref="bool"/> telling whether a given integral value, or its name as a string, exists in a specified enumeration.</summary> /// <param name="enumType">An enumeration type.</param> /// <param name="value">The value or name of a constant in <paramref name="enumType"/>.</param> /// <returns><see langword="true"/> if a constant in <paramref name="enumType"/> has a value equal to value; otherwise, <see langword="false"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="enumType"/> or <paramref name="value"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException"> /// <paramref name="enumType"/> is not an <see cref="Enum"/>, /// or the type of <paramref name="value"/> is an enumeration but it is not an enumeration of type <paramref name="enumType"/>, /// or the type of <paramref name="value"/> is not an underlying type of <paramref name="enumType"/>. /// </exception> public static bool IsDefined(Type enumType, object value) { ArgumentNullException.ThrowIfNull(enumType); return enumType.IsEnumDefined(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int FindDefinedIndex<TStorage>(TStorage[] values, TStorage value) where TStorage : struct, IEquatable<TStorage>, IComparable<TStorage> { // Binary searching has a higher constant overhead than linear searching. // For smaller enums, use IndexOf. // For larger enums, use BinarySearch. const int NumberOfValuesThreshold = 32; // This threshold can be tweaked over time as optimizations evolve. ReadOnlySpan<TStorage> span = values; return values.Length <= NumberOfValuesThreshold ? span.IndexOf(value) : SpanHelpers.BinarySearch(span, value); } /// <summary>Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.</summary> /// <param name="enumType">An enumeration type.</param> /// <param name="value">A string containing the name or value to convert.</param> /// <returns>An <see cref="object"/> of type <paramref name="enumType"/> whose value is represented by <paramref name="value"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="enumType"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException"><paramref name="enumType"/> is not an <see cref="Enum"/>.</exception> /// <exception cref="ArgumentException"><paramref name="value"/> is either an empty string or only contains white space.</exception> /// <exception cref="ArgumentException"><paramref name="value"/> is a name, but not one of the named constants defined for the enumeration.</exception> /// <exception cref="OverflowException"><paramref name="value"/> is outside the range of the underlying type of <paramref name="enumType"/></exception> public static object Parse(Type enumType, string value) => Parse(enumType, value, ignoreCase: false); /// <summary>Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.</summary> /// <param name="enumType">An enumeration type.</param> /// <param name="value">A span containing the name or value to convert.</param> /// <returns>An <see cref="object"/> of type <paramref name="enumType"/> whose value is represented by <paramref name="value"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="enumType"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException"><paramref name="enumType"/> is not an <see cref="Enum"/>.</exception> /// <exception cref="ArgumentException"><paramref name="value"/> is either an empty string or only contains white space.</exception> /// <exception cref="ArgumentException"><paramref name="value"/> is a name, but not one of the named constants defined for the enumeration.</exception> /// <exception cref="OverflowException"><paramref name="value"/> is outside the range of the underlying type of <paramref name="enumType"/></exception> public static object Parse(Type enumType, ReadOnlySpan<char> value) => Parse(enumType, value, ignoreCase: false); /// <summary> /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. /// A parameter specifies whether the operation is case-insensitive. /// </summary> /// <param name="enumType">An enumeration type.</param> /// <param name="value">A string containing the name or value to convert.</param> /// <param name="ignoreCase"><see langword="true"/> to ignore case; <see langword="false"/> to regard case.</param> /// <returns>An <see cref="object"/> of type <paramref name="enumType"/> whose value is represented by <paramref name="value"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="enumType"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException"><paramref name="enumType"/> is not an <see cref="Enum"/>.</exception> /// <exception cref="ArgumentException"><paramref name="value"/> is either an empty string or only contains white space.</exception> /// <exception cref="ArgumentException"><paramref name="value"/> is a name, but not one of the named constants defined for the enumeration.</exception> /// <exception cref="OverflowException"><paramref name="value"/> is outside the range of the underlying type of <paramref name="enumType"/></exception> public static object Parse(Type enumType, string value, bool ignoreCase) { if (value is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value); } bool success = TryParse(enumType, value.AsSpan(), ignoreCase, throwOnFailure: true, out object? result); Debug.Assert(success && result is not null); return result; } /// <summary> /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. /// A parameter specifies whether the operation is case-insensitive. /// </summary> /// <param name="enumType">An enumeration type.</param> /// <param name="value">A span containing the name or value to convert.</param> /// <param name="ignoreCase"><see langword="true"/> to ignore case; <see langword="false"/> to regard case.</param> /// <returns>An <see cref="object"/> of type <paramref name="enumType"/> whose value is represented by <paramref name="value"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="enumType"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException"><paramref name="enumType"/> is not an <see cref="Enum"/>.</exception> /// <exception cref="ArgumentException"><paramref name="value"/> is either an empty string or only contains white space.</exception> /// <exception cref="ArgumentException"><paramref name="value"/> is a name, but not one of the named constants defined for the enumeration.</exception> /// <exception cref="OverflowException"><paramref name="value"/> is outside the range of the underlying type of <paramref name="enumType"/></exception> public static object Parse(Type enumType, ReadOnlySpan<char> value, bool ignoreCase) { bool success = TryParse(enumType, value, ignoreCase, throwOnFailure: true, out object? result); Debug.Assert(success && result is not null); return result; } /// <summary>Converts the string representation of the name or numeric value of one or more enumerated constants specified by <typeparamref name="TEnum"/> to an equivalent enumerated object.</summary> /// <typeparam name="TEnum">An enumeration type.</typeparam> /// <param name="value">A string containing the name or value to convert.</param> /// <returns><typeparamref name="TEnum"/> An object of type <typeparamref name="TEnum"/> whose value is represented by <paramref name="value"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException"><typeparamref name="TEnum"/> is not an <see cref="Enum"/> type</exception> /// <exception cref="ArgumentException"><paramref name="value"/> does not contain enumeration information</exception> public static TEnum Parse<TEnum>(string value) where TEnum : struct => Parse<TEnum>(value, ignoreCase: false); /// <summary>Converts the span of chars representation of the name or numeric value of one or more enumerated constants specified by <typeparamref name="TEnum"/> to an equivalent enumerated object.</summary> /// <typeparam name="TEnum">An enumeration type.</typeparam> /// <param name="value">A span containing the name or value to convert.</param> /// <returns><typeparamref name="TEnum"/> An object of type <typeparamref name="TEnum"/> whose value is represented by <paramref name="value"/>.</returns> /// <exception cref="ArgumentException"><typeparamref name="TEnum"/> is not an <see cref="Enum"/> type</exception> /// <exception cref="ArgumentException"><paramref name="value"/> does not contain enumeration information</exception> public static TEnum Parse<TEnum>(ReadOnlySpan<char> value) where TEnum : struct => Parse<TEnum>(value, ignoreCase: false); /// <summary> /// Converts the string representation of the name or numeric value of one or more enumerated constants specified by <typeparamref name="TEnum"/> to an equivalent enumerated object. /// A parameter specifies whether the operation is case-insensitive. /// </summary> /// <typeparam name="TEnum">An enumeration type.</typeparam> /// <param name="value">A string containing the name or value to convert.</param> /// <param name="ignoreCase"><see langword="true"/> to ignore case; <see langword="false"/> to regard case.</param> /// <returns><typeparamref name="TEnum"/> An object of type <typeparamref name="TEnum"/> whose value is represented by <paramref name="value"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException"><typeparamref name="TEnum"/> is not an <see cref="Enum"/> type</exception> /// <exception cref="ArgumentException"><paramref name="value"/> does not contain enumeration information</exception> public static TEnum Parse<TEnum>(string value, bool ignoreCase) where TEnum : struct { if (value is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value); } bool success = TryParse(value.AsSpan(), ignoreCase, throwOnFailure: true, out TEnum result); Debug.Assert(success); return result; } /// <summary> /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants specified by <typeparamref name="TEnum"/> to an equivalent enumerated object. /// A parameter specifies whether the operation is case-insensitive. /// </summary> /// <typeparam name="TEnum">An enumeration type.</typeparam> /// <param name="value">A span containing the name or value to convert.</param> /// <param name="ignoreCase"><see langword="true"/> to ignore case; <see langword="false"/> to regard case.</param> /// <returns><typeparamref name="TEnum"/> An object of type <typeparamref name="TEnum"/> whose value is represented by <paramref name="value"/>.</returns> /// <exception cref="ArgumentException"><typeparamref name="TEnum"/> is not an <see cref="Enum"/> type</exception> /// <exception cref="ArgumentException"><paramref name="value"/> does not contain enumeration information</exception> public static TEnum Parse<TEnum>(ReadOnlySpan<char> value, bool ignoreCase) where TEnum : struct { bool success = TryParse(value, ignoreCase, throwOnFailure: true, out TEnum result); Debug.Assert(success); return result; } /// <summary>Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.</summary> /// <param name="enumType">The enum type to use for parsing.</param> /// <param name="value">The string representation of the name or numeric value of one or more enumerated constants.</param> /// <param name="result">When this method returns <see langword="true"/>, an object containing an enumeration constant representing the parsed value.</param> /// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns> public static bool TryParse(Type enumType, string? value, [NotNullWhen(true)] out object? result) => TryParse(enumType, value, ignoreCase: false, out result); /// <summary>Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.</summary> /// <param name="enumType">The enum type to use for parsing.</param> /// <param name="value">The span representation of the name or numeric value of one or more enumerated constants.</param> /// <param name="result">When this method returns <see langword="true"/>, an object containing an enumeration constant representing the parsed value.</param> /// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns> public static bool TryParse(Type enumType, ReadOnlySpan<char> value, [NotNullWhen(true)] out object? result) => TryParse(enumType, value, ignoreCase: false, out result); /// <summary> /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. /// A parameter specifies whether the operation is case-insensitive. /// </summary> /// <param name="enumType">The enum type to use for parsing.</param> /// <param name="value">The string representation of the name or numeric value of one or more enumerated constants.</param> /// <param name="ignoreCase"><see langword="true"/> to read <paramref name="enumType"/> in case insensitive mode; <see langword="false"/> to read <paramref name="enumType"/> in case sensitive mode.</param> /// <param name="result">When this method returns <see langword="true"/>, an object containing an enumeration constant representing the parsed value.</param> /// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns> public static bool TryParse(Type enumType, string? value, bool ignoreCase, [NotNullWhen(true)] out object? result) { if (value is not null) { return TryParse(enumType, value.AsSpan(), ignoreCase, throwOnFailure: false, out result); } result = null; return false; } /// <summary> /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. /// A parameter specifies whether the operation is case-insensitive. /// </summary> /// <param name="enumType">The enum type to use for parsing.</param> /// <param name="value">The span representation of the name or numeric value of one or more enumerated constants.</param> /// <param name="ignoreCase"><see langword="true"/> to read <paramref name="enumType"/> in case insensitive mode; <see langword="false"/> to read <paramref name="enumType"/> in case sensitive mode.</param> /// <param name="result">When this method returns <see langword="true"/>, an object containing an enumeration constant representing the parsed value.</param> /// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns> public static bool TryParse(Type enumType, ReadOnlySpan<char> value, bool ignoreCase, [NotNullWhen(true)] out object? result) => TryParse(enumType, value, ignoreCase, throwOnFailure: false, out result); /// <summary>Core implementation for all non-generic {Try}Parse methods.</summary> private static unsafe bool TryParse(Type enumType, ReadOnlySpan<char> value, bool ignoreCase, bool throwOnFailure, [NotNullWhen(true)] out object? result) { bool parsed = false; long longScratch = 0; // Validation on the enum type itself. Failures here are considered non-parsing failures // and thus always throw rather than returning false. RuntimeType rt = ValidateRuntimeType(enumType); switch (InternalGetCorElementType(rt)) { case CorElementType.ELEMENT_TYPE_I1: parsed = TryParseByValueOrName<sbyte, byte>(rt, value, ignoreCase, throwOnFailure, out *(sbyte*)&longScratch); longScratch = *(sbyte*)&longScratch; break; case CorElementType.ELEMENT_TYPE_U1: parsed = TryParseByValueOrName<byte, byte>(rt, value, ignoreCase, throwOnFailure, out *(byte*)&longScratch); longScratch = *(byte*)&longScratch; break; case CorElementType.ELEMENT_TYPE_I2: parsed = TryParseByValueOrName<short, ushort>(rt, value, ignoreCase, throwOnFailure, out *(short*)&longScratch); longScratch = *(short*)&longScratch; break; case CorElementType.ELEMENT_TYPE_U2: parsed = TryParseByValueOrName<ushort, ushort>(rt, value, ignoreCase, throwOnFailure, out *(ushort*)&longScratch); longScratch = *(ushort*)&longScratch; break; case CorElementType.ELEMENT_TYPE_I4: parsed = TryParseByValueOrName<int, uint>(rt, value, ignoreCase, throwOnFailure, out *(int*)&longScratch); longScratch = *(int*)&longScratch; break; case CorElementType.ELEMENT_TYPE_U4: parsed = TryParseByValueOrName<uint, uint>(rt, value, ignoreCase, throwOnFailure, out *(uint*)&longScratch); longScratch = *(uint*)&longScratch; break; case CorElementType.ELEMENT_TYPE_I8: parsed = TryParseByValueOrName<long, ulong>(rt, value, ignoreCase, throwOnFailure, out longScratch); break; case CorElementType.ELEMENT_TYPE_U8: parsed = TryParseByValueOrName<ulong, ulong>(rt, value, ignoreCase, throwOnFailure, out *(ulong*)&longScratch); break; default: parsed = TryParseRareTypes(rt, value, ignoreCase, throwOnFailure, out longScratch); break; } result = parsed ? InternalBoxEnum(rt.TypeHandle, longScratch) : null; return parsed; [MethodImpl(MethodImplOptions.NoInlining)] static bool TryParseRareTypes(RuntimeType rt, ReadOnlySpan<char> value, bool ignoreCase, bool throwOnFailure, [NotNullWhen(true)] out long result) { #if RARE_ENUMS bool parsed; switch (InternalGetCorElementType(rt)) { case CorElementType.ELEMENT_TYPE_R4: { parsed = TryParseRareTypeByValueOrName<float, float>(rt, value, ignoreCase, throwOnFailure, out float localResult); result = BitConverter.SingleToInt32Bits(localResult); } break; case CorElementType.ELEMENT_TYPE_R8: { parsed = TryParseRareTypeByValueOrName<double, double>(rt, value, ignoreCase, throwOnFailure, out double localResult); result = BitConverter.DoubleToInt64Bits(localResult); } break; case CorElementType.ELEMENT_TYPE_I: { parsed = TryParseRareTypeByValueOrName<nint, nuint>(rt, value, ignoreCase, throwOnFailure, out nint localResult); result = localResult; } break; case CorElementType.ELEMENT_TYPE_U: { parsed = TryParseRareTypeByValueOrName<nuint, nuint>(rt, value, ignoreCase, throwOnFailure, out nuint localResult); result = (long)localResult; } break; case CorElementType.ELEMENT_TYPE_CHAR: { parsed = TryParseRareTypeByValueOrName<char, char>(rt, value, ignoreCase, throwOnFailure, out char localResult); result = localResult; } break; default: throw CreateUnknownEnumTypeException(); } return parsed; #else throw CreateUnknownEnumTypeException(); #endif } } /// <summary>Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.</summary> /// <typeparam name="TEnum"></typeparam> /// <param name="value">The string representation of the name or numeric value of one or more enumerated constants.</param> /// <param name="result">When this method returns <see langword="true"/>, an object containing an enumeration constant representing the parsed value.</param> /// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns> /// <exception cref="ArgumentException"><typeparamref name="TEnum"/> is not an enumeration type</exception> public static bool TryParse<TEnum>([NotNullWhen(true)] string? value, out TEnum result) where TEnum : struct => TryParse(value, ignoreCase: false, out result); /// <summary>Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.</summary> /// <typeparam name="TEnum"></typeparam> /// <param name="value">The span of chars representation of the name or numeric value of one or more enumerated constants.</param> /// <param name="result">When this method returns <see langword="true"/>, an object containing an enumeration constant representing the parsed value.</param> /// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns> /// <exception cref="ArgumentException"><typeparamref name="TEnum"/> is not an enumeration type</exception> public static bool TryParse<TEnum>(ReadOnlySpan<char> value, out TEnum result) where TEnum : struct => TryParse(value, ignoreCase: false, throwOnFailure: false, out result); /// <summary> /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. /// A parameter specifies whether the operation is case-sensitive. /// </summary> /// <typeparam name="TEnum"></typeparam> /// <param name="value">The string representation of the name or numeric value of one or more enumerated constants.</param> /// <param name="ignoreCase"><see langword="true"/> to ignore case; <see langword="false"/> to consider case.</param> /// <param name="result">When this method returns <see langword="true"/>, an object containing an enumeration constant representing the parsed value.</param> /// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns> /// <exception cref="ArgumentException"><typeparamref name="TEnum"/> is not an enumeration type</exception> public static bool TryParse<TEnum>([NotNullWhen(true)] string? value, bool ignoreCase, out TEnum result) where TEnum : struct { if (value is not null) { return TryParse(value.AsSpan(), ignoreCase, throwOnFailure: false, out result); } result = default; return false; } /// <summary> /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. /// A parameter specifies whether the operation is case-sensitive. /// </summary> /// <typeparam name="TEnum"></typeparam> /// <param name="value">The span representation of the name or numeric value of one or more enumerated constants.</param> /// <param name="ignoreCase"><see langword="true"/> to ignore case; <see langword="false"/> to consider case.</param> /// <param name="result">When this method returns <see langword="true"/>, an object containing an enumeration constant representing the parsed value.</param> /// <returns><see langword="true"/> if the conversion succeeded; <see langword="false"/> otherwise.</returns> /// <exception cref="ArgumentException"><typeparamref name="TEnum"/> is not an enumeration type</exception> public static bool TryParse<TEnum>(ReadOnlySpan<char> value, bool ignoreCase, out TEnum result) where TEnum : struct => TryParse(value, ignoreCase, throwOnFailure: false, out result); /// <summary>Core implementation for all generic {Try}Parse methods.</summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single call private static bool TryParse<TEnum>(ReadOnlySpan<char> value, bool ignoreCase, bool throwOnFailure, out TEnum result) where TEnum : struct { // Validation on the enum type itself. Failures here are considered non-parsing failures // and thus always throw rather than returning false. if (!typeof(TEnum).IsEnum) // with IsEnum being an intrinsic, this whole block will be eliminated for all meaningful cases { throw new ArgumentException(SR.Arg_MustBeEnum, nameof(TEnum)); } Unsafe.SkipInit(out result); RuntimeType rt = (RuntimeType)typeof(TEnum); Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); if (underlyingType == typeof(sbyte)) return TryParseByValueOrName<sbyte, byte>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, sbyte>(ref result)); if (underlyingType == typeof(byte)) return TryParseByValueOrName<byte, byte>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, byte>(ref result)); if (underlyingType == typeof(short)) return TryParseByValueOrName<short, ushort>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, short>(ref result)); if (underlyingType == typeof(ushort)) return TryParseByValueOrName<ushort, ushort>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, ushort>(ref result)); if (underlyingType == typeof(int)) return TryParseByValueOrName<int, uint>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, int>(ref result)); if (underlyingType == typeof(uint)) return TryParseByValueOrName<uint, uint>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, uint>(ref result)); if (underlyingType == typeof(long)) return TryParseByValueOrName<long, ulong>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, long>(ref result)); if (underlyingType == typeof(ulong)) return TryParseByValueOrName<ulong, ulong>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, ulong>(ref result)); #if RARE_ENUMS if (underlyingType == typeof(nint)) return TryParseRareTypeByValueOrName<nint, nuint>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, nint>(ref result)); if (underlyingType == typeof(nuint)) return TryParseRareTypeByValueOrName<nuint, nuint>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, nuint>(ref result)); if (underlyingType == typeof(float)) return TryParseRareTypeByValueOrName<float, float>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, float>(ref result)); if (underlyingType == typeof(double)) return TryParseRareTypeByValueOrName<double, double>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, double>(ref result)); if (underlyingType == typeof(char)) return TryParseRareTypeByValueOrName<char, char>(rt, value, ignoreCase, throwOnFailure, out Unsafe.As<TEnum, char>(ref result)); #endif throw CreateUnknownEnumTypeException(); } /// <summary>Core implementation for all {Try}Parse methods, both generic and non-generic, parsing either by value or by name.</summary> private static bool TryParseByValueOrName<TUnderlying, TStorage>( RuntimeType enumType, ReadOnlySpan<char> value, bool ignoreCase, bool throwOnFailure, out TUnderlying result) where TUnderlying : unmanaged, IBinaryIntegerParseAndFormatInfo<TUnderlying> where TStorage : unmanaged, IBinaryIntegerParseAndFormatInfo<TStorage> { AssertValidGenerics<TUnderlying, TStorage>(); if (!value.IsEmpty) { char c = value[0]; if (char.IsWhiteSpace(c)) { value = value.TrimStart(); if (value.IsEmpty) { goto ParseFailure; } c = value[0]; } if (!char.IsAsciiDigit(c) && c != '-' && c != '+') { Unsafe.SkipInit(out result); return TryParseByName(enumType, value, ignoreCase, throwOnFailure, out Unsafe.As<TUnderlying, TStorage>(ref result)); } NumberFormatInfo numberFormat = NumberFormatInfo.InvariantInfo; const NumberStyles NumberStyle = NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite; Number.ParsingStatus status = Number.TryParseBinaryIntegerStyle(value, NumberStyle, numberFormat, out result, out _); if (status == Number.ParsingStatus.OK) { return true; } if (status != Number.ParsingStatus.Overflow) { Unsafe.SkipInit(out result); return TryParseByName(enumType, value, ignoreCase, throwOnFailure, out Unsafe.As<TUnderlying, TStorage>(ref result)); } if (throwOnFailure) { Number.ThrowOverflowException<TUnderlying>(); } } ParseFailure: if (throwOnFailure) { ThrowInvalidEmptyParseArgument(); } result = default; return false; } private static bool TryParseRareTypeByValueOrName<TUnderlying, TStorage>( RuntimeType enumType, ReadOnlySpan<char> value, bool ignoreCase, bool throwOnFailure, out TUnderlying result) where TUnderlying : struct, INumber<TUnderlying>, IBitwiseOperators<TUnderlying, TUnderlying, TUnderlying>, IMinMaxValue<TUnderlying> where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage>, IMinMaxValue<TStorage> { AssertValidGenerics<TUnderlying, TStorage>(); if (!value.IsEmpty) { char c = value[0]; if (char.IsWhiteSpace(c)) { value = value.TrimStart(); if (value.IsEmpty) { goto ParseFailure; } c = value[0]; } if (!char.IsAsciiDigit(c) && c != '-' && c != '+') { Unsafe.SkipInit(out result); return TryParseByName(enumType, value, ignoreCase, throwOnFailure, out Unsafe.As<TUnderlying, TStorage>(ref result)); } #if RARE_ENUMS Number.ParsingStatus status; Type underlyingType = GetUnderlyingType(enumType); try { result = (TUnderlying)ToObject(enumType, Convert.ChangeType(value.ToString(), underlyingType, CultureInfo.InvariantCulture)); return true; } catch (FormatException) { status = Number.ParsingStatus.Failed; // e.g. tlbimp enums that can have values of the form "3D" } catch when (!throwOnFailure) { status = Number.ParsingStatus.Overflow; // fall through to returning failure } if (status != Number.ParsingStatus.Overflow) { Unsafe.SkipInit(out result); return TryParseByName(enumType, value, ignoreCase, throwOnFailure, out Unsafe.As<TUnderlying, TStorage>(ref result)); } if (throwOnFailure) { ThrowHelper.ThrowOverflowException(); } #else throw CreateUnknownEnumTypeException(); #endif } ParseFailure: if (throwOnFailure) { ThrowInvalidEmptyParseArgument(); } result = default; return false; } /// <summary>Handles just the name parsing portion of <see cref="TryParseRareTypeByValueOrName"/>.</summary> private static bool TryParseByName<TStorage>(RuntimeType enumType, ReadOnlySpan<char> value, bool ignoreCase, bool throwOnFailure, out TStorage result) where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage> { ReadOnlySpan<char> originalValue = value; // Find the field. Let's assume that these are always static classes because the class is an enum. EnumInfo<TStorage> enumInfo = GetEnumInfo<TStorage>(enumType); string[] enumNames = enumInfo.Names; TStorage[] enumValues = enumInfo.Values; bool parsed = true; TStorage localResult = default; while (value.Length > 0) { // Find the next separator. ReadOnlySpan<char> subvalue; int endIndex = value.IndexOf(EnumSeparatorChar); if (endIndex < 0) { // No next separator; use the remainder as the next value. subvalue = value.Trim(); value = default; } else if (endIndex != value.Length - 1) { // Found a separator before the last char. subvalue = value.Slice(0, endIndex).Trim(); value = value.Slice(endIndex + 1); } else { // Last char was a separator, which is invalid. parsed = false; break; } // Try to match this substring against each enum name bool success = false; if (ignoreCase) { for (int i = 0; i < enumNames.Length; i++) { if (subvalue.EqualsOrdinalIgnoreCase(enumNames[i])) { localResult |= enumValues[i]; success = true; break; } } } else { for (int i = 0; i < enumNames.Length; i++) { if (subvalue.SequenceEqual(enumNames[i])) { localResult |= enumValues[i]; success = true; break; } } } if (!success) { parsed = false; break; } } if (parsed) { result = localResult; return true; } if (throwOnFailure) { throw new ArgumentException(SR.Format(SR.Arg_EnumValueNotFound, originalValue.ToString())); } result = default; return false; } /// <summary> /// Silently convert the <paramref name="value"/> to a <see cref="ulong"/> from the other base types for enum without /// throwing an exception (other than for an unknown enum type). /// </summary> /// <remarks>This is needed since the Convert functions do overflow checks.</remarks> internal static ulong ToUInt64(object value) { switch (Convert.GetTypeCode(value)) { case TypeCode.SByte: return (ulong)(sbyte)value; case TypeCode.Byte: return (byte)value; case TypeCode.Int16: return (ulong)(short)value; case TypeCode.UInt16: return (ushort)value; case TypeCode.Int32: return (ulong)(int)value; case TypeCode.Int64: return (ulong)(long)value; case TypeCode.UInt32: return (uint)value; case TypeCode.UInt64: return (ulong)value; case TypeCode.Char: return (char)value; }; if (value is not null) { Type valueType = value.GetType(); if (valueType.IsEnum) { valueType = valueType.GetEnumUnderlyingType(); } if (valueType == typeof(nint)) return (ulong)(nint)value; if (valueType == typeof(nuint)) return (nuint)value; } throw CreateUnknownEnumTypeException(); } /// <summary>Gets a boxed underlying value of this enum.</summary> internal object GetValue() { ref byte data = ref this.GetRawData(); return InternalGetCorElementType() switch { CorElementType.ELEMENT_TYPE_I1 => Unsafe.As<byte, sbyte>(ref data), CorElementType.ELEMENT_TYPE_U1 => data, CorElementType.ELEMENT_TYPE_I2 => Unsafe.As<byte, short>(ref data), CorElementType.ELEMENT_TYPE_U2 => Unsafe.As<byte, ushort>(ref data), CorElementType.ELEMENT_TYPE_I4 => Unsafe.As<byte, int>(ref data), CorElementType.ELEMENT_TYPE_U4 => Unsafe.As<byte, uint>(ref data), CorElementType.ELEMENT_TYPE_I8 => Unsafe.As<byte, long>(ref data), CorElementType.ELEMENT_TYPE_U8 => Unsafe.As<byte, ulong>(ref data), #if RARE_ENUMS CorElementType.ELEMENT_TYPE_R4 => Unsafe.As<byte, float>(ref data), CorElementType.ELEMENT_TYPE_R8 => Unsafe.As<byte, double>(ref data), CorElementType.ELEMENT_TYPE_I => Unsafe.As<byte, IntPtr>(ref data), CorElementType.ELEMENT_TYPE_U => Unsafe.As<byte, UIntPtr>(ref data), CorElementType.ELEMENT_TYPE_CHAR => Unsafe.As<byte, char>(ref data), CorElementType.ELEMENT_TYPE_BOOLEAN => Unsafe.As<byte, bool>(ref data), #endif _ => throw CreateUnknownEnumTypeException(), }; } /// <inheritdoc/> [Intrinsic] public override bool Equals([NotNullWhen(true)] object? obj) { if (obj is null) return false; if (this == obj) return true; if (this.GetType() != obj.GetType()) return false; ref byte pThisValue = ref this.GetRawData(); ref byte pOtherValue = ref obj.GetRawData(); switch (InternalGetCorElementType()) { case CorElementType.ELEMENT_TYPE_I1: case CorElementType.ELEMENT_TYPE_U1: return pThisValue == pOtherValue; case CorElementType.ELEMENT_TYPE_I2: case CorElementType.ELEMENT_TYPE_U2: return Unsafe.As<byte, ushort>(ref pThisValue) == Unsafe.As<byte, ushort>(ref pOtherValue); case CorElementType.ELEMENT_TYPE_I4: case CorElementType.ELEMENT_TYPE_U4: return Unsafe.As<byte, uint>(ref pThisValue) == Unsafe.As<byte, uint>(ref pOtherValue); case CorElementType.ELEMENT_TYPE_I8: case CorElementType.ELEMENT_TYPE_U8: return Unsafe.As<byte, ulong>(ref pThisValue) == Unsafe.As<byte, ulong>(ref pOtherValue); #if RARE_ENUMS case CorElementType.ELEMENT_TYPE_BOOLEAN: goto case CorElementType.ELEMENT_TYPE_U1; case CorElementType.ELEMENT_TYPE_CHAR: goto case CorElementType.ELEMENT_TYPE_U2; case CorElementType.ELEMENT_TYPE_R4: goto case CorElementType.ELEMENT_TYPE_U4; case CorElementType.ELEMENT_TYPE_R8: goto case CorElementType.ELEMENT_TYPE_U8; case CorElementType.ELEMENT_TYPE_I: case CorElementType.ELEMENT_TYPE_U: #if TARGET_32BIT goto case CorElementType.ELEMENT_TYPE_U4; #else goto case CorElementType.ELEMENT_TYPE_U8; #endif #endif default: Debug.Fail("Unknown enum underlying type"); return false; } } /// <inheritdoc/> public override int GetHashCode() { // CONTRACT with the runtime: GetHashCode of enum types is implemented as GetHashCode of the underlying type. // The runtime can bypass calls to Enum::GetHashCode and call the underlying type's GetHashCode directly // to avoid boxing the enum. ref byte data = ref this.GetRawData(); return InternalGetCorElementType() switch { CorElementType.ELEMENT_TYPE_I1 => Unsafe.As<byte, sbyte>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U1 => data.GetHashCode(), CorElementType.ELEMENT_TYPE_I2 => Unsafe.As<byte, short>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U2 => Unsafe.As<byte, ushort>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_I4 => Unsafe.As<byte, int>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U4 => Unsafe.As<byte, uint>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_I8 => Unsafe.As<byte, long>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U8 => Unsafe.As<byte, ulong>(ref data).GetHashCode(), #if RARE_ENUMS CorElementType.ELEMENT_TYPE_R4 => Unsafe.As<byte, float>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_R8 => Unsafe.As<byte, double>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_I => Unsafe.As<byte, IntPtr>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U => Unsafe.As<byte, UIntPtr>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_CHAR => Unsafe.As<byte, char>(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_BOOLEAN => Unsafe.As<byte, bool>(ref data).GetHashCode(), #endif _ => throw CreateUnknownEnumTypeException(), }; } /// <inheritdoc/> public int CompareTo(object? target) { if (target == this) return 0; if (target == null) return 1; // all values are greater than null if (GetType() != target.GetType()) throw new ArgumentException(SR.Format(SR.Arg_EnumAndObjectMustBeSameType, target.GetType(), GetType())); ref byte pThisValue = ref this.GetRawData(); ref byte pTargetValue = ref target.GetRawData(); switch (InternalGetCorElementType()) { case CorElementType.ELEMENT_TYPE_I1: return Unsafe.As<byte, sbyte>(ref pThisValue).CompareTo(Unsafe.As<byte, sbyte>(ref pTargetValue)); case CorElementType.ELEMENT_TYPE_U1: return pThisValue.CompareTo(pTargetValue); case CorElementType.ELEMENT_TYPE_I2: return Unsafe.As<byte, short>(ref pThisValue).CompareTo(Unsafe.As<byte, short>(ref pTargetValue)); case CorElementType.ELEMENT_TYPE_U2: return Unsafe.As<byte, ushort>(ref pThisValue).CompareTo(Unsafe.As<byte, ushort>(ref pTargetValue)); case CorElementType.ELEMENT_TYPE_I4: return Unsafe.As<byte, int>(ref pThisValue).CompareTo(Unsafe.As<byte, int>(ref pTargetValue)); case CorElementType.ELEMENT_TYPE_U4: return Unsafe.As<byte, uint>(ref pThisValue).CompareTo(Unsafe.As<byte, uint>(ref pTargetValue)); case CorElementType.ELEMENT_TYPE_I8: return Unsafe.As<byte, long>(ref pThisValue).CompareTo(Unsafe.As<byte, long>(ref pTargetValue)); case CorElementType.ELEMENT_TYPE_U8: return Unsafe.As<byte, ulong>(ref pThisValue).CompareTo(Unsafe.As<byte, ulong>(ref pTargetValue)); #if RARE_ENUMS case CorElementType.ELEMENT_TYPE_R4: return Unsafe.As<byte, float>(ref pThisValue).CompareTo(Unsafe.As<byte, float>(ref pTargetValue)); case CorElementType.ELEMENT_TYPE_R8: return Unsafe.As<byte, double>(ref pThisValue).CompareTo(Unsafe.As<byte, double>(ref pTargetValue)); case CorElementType.ELEMENT_TYPE_BOOLEAN: goto case CorElementType.ELEMENT_TYPE_U1; case CorElementType.ELEMENT_TYPE_CHAR: goto case CorElementType.ELEMENT_TYPE_U2; #if TARGET_32BIT case CorElementType.ELEMENT_TYPE_I: goto case CorElementType.ELEMENT_TYPE_I4; case CorElementType.ELEMENT_TYPE_U: goto case CorElementType.ELEMENT_TYPE_U4; #else case CorElementType.ELEMENT_TYPE_I: goto case CorElementType.ELEMENT_TYPE_I8; case CorElementType.ELEMENT_TYPE_U: goto case CorElementType.ELEMENT_TYPE_U8; #endif #endif default: Debug.Fail("Unknown enum underlying type"); return 0; } } /// <summary>Converts the value of this instance to its equivalent string representation.</summary> /// <remarks>The string representation of the value of this instance.</remarks> public override string ToString() { RuntimeType enumType = (RuntimeType)GetType(); ref byte rawData = ref this.GetRawData(); return InternalGetCorElementType() switch { // Inlined for the most common base types CorElementType.ELEMENT_TYPE_I1 => ToString<sbyte, byte>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_U1 => ToStringInlined<byte, byte>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_I2 => ToString<short, ushort>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_U2 => ToString<ushort, ushort>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_I4 => ToStringInlined<int, uint>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_U4 => ToString<uint, uint>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_I8 => ToString<long, ulong>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_U8 => ToString<ulong, ulong>(enumType, ref rawData), _ => HandleRareTypes(enumType, ref rawData) }; [MethodImpl(MethodImplOptions.NoInlining)] static string HandleRareTypes(RuntimeType enumType, ref byte rawData) => #if RARE_ENUMS InternalGetCorElementType(enumType) switch { CorElementType.ELEMENT_TYPE_R4 => ToString<float, float>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_R8 => ToString<double, double>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_I => ToString<nint, nuint>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_U => ToString<nuint, nuint>(enumType, ref rawData), CorElementType.ELEMENT_TYPE_CHAR => ToString<char, char>(enumType, ref rawData), _ => throw CreateUnknownEnumTypeException(), }; #else throw CreateUnknownEnumTypeException(); #endif } /// <summary>Converts the value of this instance to its equivalent string representation using the specified format.</summary> /// <param name="format">A format string.</param> /// <returns>The string representation of the value of this instance as specified by <paramref name="format"/>.</returns> /// <exception cref="FormatException"><paramref name="format"/> contains an invalid specification.</exception> /// <exception cref="InvalidOperationException"><paramref name="format"/> equals "X" or "x", but the enumeration type is unknown.</exception> public string ToString([StringSyntax(StringSyntaxAttribute.EnumFormat)] string? format) { if (string.IsNullOrEmpty(format)) { return ToString(); } if (format.Length == 1) { char formatChar = format[0]; RuntimeType enumType = (RuntimeType)GetType(); ref byte rawData = ref this.GetRawData(); return InternalGetCorElementType() switch { // Inlined for the most common base types CorElementType.ELEMENT_TYPE_I1 => ToString<sbyte, byte>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U1 => ToStringInlined<byte, byte>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_I2 => ToString<short, ushort>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U2 => ToString<ushort, ushort>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_I4 => ToStringInlined<int, uint>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U4 => ToString<uint, uint>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_I8 => ToString<long, ulong>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U8 => ToString<ulong, ulong>(enumType, formatChar, ref rawData), _ => HandleRareTypes(enumType, formatChar, ref rawData) }; } throw CreateInvalidFormatSpecifierException(); [MethodImpl(MethodImplOptions.NoInlining)] static string HandleRareTypes(RuntimeType enumType, char formatChar, ref byte rawData) => #if RARE_ENUMS InternalGetCorElementType(enumType) switch { CorElementType.ELEMENT_TYPE_R4 => ToString<float, float>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_R8 => ToString<double, double>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_I => ToString<nint, nuint>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U => ToString<nuint, nuint>(enumType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_CHAR => ToString<char, char>(enumType, formatChar, ref rawData), _ => throw CreateUnknownEnumTypeException(), }; #else throw CreateUnknownEnumTypeException(); #endif } /// <summary>This method overload is obsolete; use <see cref="ToString()"/>.</summary> [Obsolete("The provider argument is not used. Use ToString() instead.")] public string ToString(IFormatProvider? provider) => ToString(); /// <summary>This method overload is obsolete; use <see cref="ToString(string)"/>.</summary> [Obsolete("The provider argument is not used. Use ToString(String) instead.")] public string ToString([StringSyntax(StringSyntaxAttribute.EnumFormat)] string? format, IFormatProvider? provider) => ToString(format); [MethodImpl(MethodImplOptions.NoInlining)] // avoid bloating call sites for underlying types and/or call sites that aren't perf critical private static string ToString<TUnderlying, TStorage>(RuntimeType enumType, ref byte rawData) where TUnderlying : struct, INumber<TUnderlying>, IBitwiseOperators<TUnderlying, TUnderlying, TUnderlying> where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage> => ToStringInlined<TUnderlying, TStorage>(enumType, ref rawData); /// <summary>Converts the value of an enum to its equivalent string representation using the default format.</summary> /// <typeparam name="TUnderlying">The underlying type for this enum.</typeparam> /// <typeparam name="TStorage">The type stored in the EnumInfo. This will either be the same as <typeparamref name="TUnderlying"/> or its unsigned counterpart if <typeparamref name="TUnderlying"/> is a signed integer.</typeparam> /// <param name="enumType">The enum type.</param> /// <param name="rawData">A reference to the enum's value.</param> /// <returns>The string representation of the value of this instance.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] // used for most important types at most important call sites private static string ToStringInlined<TUnderlying, TStorage>(RuntimeType enumType, ref byte rawData) where TUnderlying : struct, INumber<TUnderlying>, IBitwiseOperators<TUnderlying, TUnderlying, TUnderlying> where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage> { AssertValidGenerics<TUnderlying, TStorage>(); TStorage value = Unsafe.As<byte, TStorage>(ref rawData); EnumInfo<TStorage> enumInfo = GetEnumInfo<TStorage>(enumType); string? result = enumInfo.HasFlagsAttribute ? FormatFlagNames(enumInfo, value) : GetNameInlined(enumInfo, value); return result ?? Unsafe.BitCast<TStorage, TUnderlying>(value).ToString()!; } [MethodImpl(MethodImplOptions.NoInlining)] // avoid bloating call sites for underlying types and/or call sites that aren't perf critical private static string ToString<TUnderlying, TStorage>(RuntimeType enumType, char format, ref byte rawData) where TUnderlying : struct, INumber<TUnderlying>, IBitwiseOperators<TUnderlying, TUnderlying, TUnderlying>, IMinMaxValue<TUnderlying> where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage>, IMinMaxValue<TStorage> => ToStringInlined<TUnderlying, TStorage>(enumType, format, ref rawData); /// <summary>Converts the value of an enum to its equivalent string representation using the default format.</summary> /// <typeparam name="TUnderlying">The underlying type for this enum.</typeparam> /// <typeparam name="TStorage">The type stored in the EnumInfo. This will either be the same as <typeparamref name="TUnderlying"/> or its unsigned counterpart if <typeparamref name="TUnderlying"/> is a signed integer.</typeparam> /// <param name="enumType">The enum type.</param> /// <param name="format">A format string.</param> /// <param name="rawData">A reference to the enum's value.</param> /// <returns>The string representation of the value of this instance as specified by <paramref name="format"/>.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] // used for most important types at most important call sites private static string ToStringInlined<TUnderlying, TStorage>(RuntimeType enumType, char format, ref byte rawData) where TUnderlying : struct, INumber<TUnderlying>, IBitwiseOperators<TUnderlying, TUnderlying, TUnderlying>, IMinMaxValue<TUnderlying> where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage>, IMinMaxValue<TStorage> { AssertValidGenerics<TUnderlying, TStorage>(); TStorage value = Unsafe.As<byte, TStorage>(ref rawData); string? result; switch (format | 0x20) { case 'g': EnumInfo<TStorage> enumInfo = GetEnumInfo<TStorage>(enumType); result = enumInfo.HasFlagsAttribute ? FormatFlagNames(enumInfo, value) : GetNameInlined(enumInfo, value); if (result is null) { goto case 'd'; } break; case 'd': result = Unsafe.BitCast<TStorage, TUnderlying>(value).ToString()!; break; case 'x': result = FormatNumberAsHex<TStorage>(ref rawData); break; case 'f': result = FormatFlagNames(GetEnumInfo<TStorage>(enumType), value); if (result is null) { goto case 'd'; } break; default: throw CreateInvalidFormatSpecifierException(); }; return result; } /// <summary>Formats the data for the underlying value as hex into a new, fixed-length string.</summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe string FormatNumberAsHex<TStorage>(ref byte data) where TStorage : struct { fixed (byte* ptr = &data) { return string.Create(sizeof(TStorage) * 2, (IntPtr)ptr, (destination, intptr) => { bool success = TryFormatNumberAsHex<TStorage>(ref *(byte*)intptr, destination, out int charsWritten); Debug.Assert(success); Debug.Assert(charsWritten == sizeof(TStorage) * 2); }); } } /// <summary>Tries to format the data for the underlying value as hex into the destination span.</summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe bool TryFormatNumberAsHex<TStorage>(ref byte data, Span<char> destination, out int charsWritten) where TStorage : struct { if (sizeof(TStorage) * 2 <= destination.Length) { if (typeof(TStorage) == typeof(byte) || typeof(TStorage) == typeof(sbyte)) { HexConverter.ToCharsBuffer(data, destination); } else if (typeof(TStorage) == typeof(ushort) || typeof(TStorage) == typeof(short) || typeof(TStorage) == typeof(char)) { ushort value = Unsafe.As<byte, ushort>(ref data); HexConverter.ToCharsBuffer((byte)(value >> 8), destination); HexConverter.ToCharsBuffer((byte)value, destination, 2); } else if (typeof(TStorage) == typeof(uint) || #if TARGET_32BIT typeof(TStorage) == typeof(nuint) || typeof(TStorage) == typeof(nint) || #endif typeof(TStorage) == typeof(int)) { uint value = Unsafe.As<byte, uint>(ref data); HexConverter.ToCharsBuffer((byte)(value >> 24), destination); HexConverter.ToCharsBuffer((byte)(value >> 16), destination, 2); HexConverter.ToCharsBuffer((byte)(value >> 8), destination, 4); HexConverter.ToCharsBuffer((byte)value, destination, 6); } else if (typeof(TStorage) == typeof(ulong) || #if TARGET_64BIT typeof(TStorage) == typeof(nuint) || typeof(TStorage) == typeof(nint) || #endif typeof(TStorage) == typeof(long)) { ulong value = Unsafe.As<byte, ulong>(ref data); HexConverter.ToCharsBuffer((byte)(value >> 56), destination); HexConverter.ToCharsBuffer((byte)(value >> 48), destination, 2); HexConverter.ToCharsBuffer((byte)(value >> 40), destination, 4); HexConverter.ToCharsBuffer((byte)(value >> 32), destination, 6); HexConverter.ToCharsBuffer((byte)(value >> 24), destination, 8); HexConverter.ToCharsBuffer((byte)(value >> 16), destination, 10); HexConverter.ToCharsBuffer((byte)(value >> 8), destination, 12); HexConverter.ToCharsBuffer((byte)value, destination, 14); } else { throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType); } charsWritten = sizeof(TStorage) * 2; return true; } charsWritten = 0; return false; } /// <summary>Converts the specified value of a specified enumerated type to its equivalent string representation according to the specified format.</summary> /// <param name="enumType">The enumeration type of the value to convert.</param> /// <param name="value">The value to convert.</param> /// <param name="format">The output format to use.</param> /// <returns>A string representation of <paramref name="value"/>.</returns> /// <exception cref="ArgumentNullException">The <paramref name="enumType"/>, <paramref name="value"/>, or <paramref name="format"/> parameter is null.</exception> /// <exception cref="ArgumentException">The <paramref name="enumType"/> parameter is not an <see cref="Enum"/> type.</exception> /// <exception cref="ArgumentException">The <paramref name="value"/> is from an enumeration that differs in type from <paramref name="enumType"/>.</exception> /// <exception cref="ArgumentException">The type of <paramref name="value"/> is not an underlying type of <paramref name="enumType"/>.</exception> /// <exception cref="FormatException"><paramref name="format"/> contains an invalid value.</exception> /// <exception cref="InvalidOperationException"><paramref name="format"/> equals "X" or "x", but the enumeration type is unknown.</exception> public static string Format(Type enumType, object value, [StringSyntax(StringSyntaxAttribute.EnumFormat)] string format) { ArgumentNullException.ThrowIfNull(value); ArgumentNullException.ThrowIfNull(format); RuntimeType rtType = ValidateRuntimeType(enumType); Type valueType = value.GetType(); if (valueType.IsEnum) { // If the value is an enum type, then it must be equivalent to the specified type. if (!valueType.IsEquivalentTo(rtType)) throw new ArgumentException(SR.Format(SR.Arg_EnumAndObjectMustBeSameType, valueType, rtType)); // If the format isn't empty, just delegate to ToString(format). The length check is necessary // here for compat, as Enum.Format prohibits a null or empty format whereas ToString(string) allows it. if (format.Length == 1) return ((Enum)value).ToString(format); } else { // The value isn't an enum type. It's either an underlying type or it's invalid, // and as an underlying type, it must match the underlying type of the enum type. Type underlyingType = GetUnderlyingType(rtType); if (valueType != underlyingType) throw new ArgumentException(SR.Format(SR.Arg_EnumFormatUnderlyingTypeAndObjectMustBeSameType, valueType, underlyingType)); // If the format isn't empty, delegate to ToString with the format. if (format.Length == 1) { char formatChar = format[0]; ref byte rawData = ref value.GetRawData(); return InternalGetCorElementType(rtType) switch { CorElementType.ELEMENT_TYPE_I1 => ToString<sbyte, byte>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U1 => ToString<byte, byte>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_I2 => ToString<short, ushort>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U2 => ToString<ushort, ushort>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_I4 => ToString<int, uint>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U4 => ToString<uint, uint>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_I8 => ToString<long, ulong>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U8 => ToString<ulong, ulong>(rtType, formatChar, ref rawData), #if RARE_ENUMS CorElementType.ELEMENT_TYPE_R4 => ToString<float, float>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_R8 => ToString<double, double>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_I => ToString<nint, nuint>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_U => ToString<nuint, nuint>(rtType, formatChar, ref rawData), CorElementType.ELEMENT_TYPE_CHAR => ToString<char, char>(rtType, formatChar, ref rawData), #endif _ => throw CreateUnknownEnumTypeException(), }; } } throw CreateInvalidFormatSpecifierException(); } /// <summary>Tries to format the value of the enum into the provided span of characters.</summary> /// <param name="destination">The span in which to write this instance's value formatted as a span of characters.</param> /// <param name="charsWritten">When this method returns, contains the number of characters that were written in destination.</param> /// <param name="format">The format specifier.</param> /// <param name="provider">An optional object that supplies culture-specific formatting information for destination. This is ignored.</param> /// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns> bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) { RuntimeType enumType = (RuntimeType)GetType(); ref byte rawData = ref this.GetRawData(); CorElementType corElementType = InternalGetCorElementType(); if (format.IsEmpty) { return corElementType switch { CorElementType.ELEMENT_TYPE_I1 => TryFormatPrimitiveDefault<sbyte, byte>(enumType, (sbyte)rawData, destination, out charsWritten), CorElementType.ELEMENT_TYPE_U1 => TryFormatPrimitiveDefault<byte, byte>(enumType, rawData, destination, out charsWritten), CorElementType.ELEMENT_TYPE_I2 => TryFormatPrimitiveDefault<short, ushort>(enumType, Unsafe.As<byte, short>(ref rawData), destination, out charsWritten), CorElementType.ELEMENT_TYPE_U2 => TryFormatPrimitiveDefault<ushort, ushort>(enumType, Unsafe.As<byte, ushort>(ref rawData), destination, out charsWritten), CorElementType.ELEMENT_TYPE_I4 => TryFormatPrimitiveDefault<int, uint>(enumType, Unsafe.As<byte, int>(ref rawData), destination, out charsWritten), CorElementType.ELEMENT_TYPE_U4 => TryFormatPrimitiveDefault<uint, uint>(enumType, Unsafe.As<byte, uint>(ref rawData), destination, out charsWritten), CorElementType.ELEMENT_TYPE_I8 => TryFormatPrimitiveDefault<long, ulong>(enumType, Unsafe.As<byte, long>(ref rawData), destination, out charsWritten), CorElementType.ELEMENT_TYPE_U8 => TryFormatPrimitiveDefault<ulong, ulong>(enumType, Unsafe.As<byte, ulong>(ref rawData), destination, out charsWritten), #if RARE_ENUMS CorElementType.ELEMENT_TYPE_R4 => TryFormatPrimitiveDefault<float, float>(enumType, Unsafe.As<byte, float>(ref rawData), destination, out charsWritten), CorElementType.ELEMENT_TYPE_R8 => TryFormatPrimitiveDefault<double, double>(enumType, Unsafe.As<byte, double>(ref rawData), destination, out charsWritten), CorElementType.ELEMENT_TYPE_I => TryFormatPrimitiveDefault<nint, nuint>(enumType, Unsafe.As<byte, nint>(ref rawData), destination, out charsWritten), CorElementType.ELEMENT_TYPE_U => TryFormatPrimitiveDefault<nuint, nuint>(enumType, Unsafe.As<byte, nuint>(ref rawData), destination, out charsWritten), CorElementType.ELEMENT_TYPE_CHAR => TryFormatPrimitiveDefault<char, char>(enumType, Unsafe.As<byte, char>(ref rawData), destination, out charsWritten), #endif _ => throw CreateUnknownEnumTypeException(), }; } else { return corElementType switch { CorElementType.ELEMENT_TYPE_I1 => TryFormatPrimitiveNonDefault<sbyte, byte>(enumType, (sbyte)rawData, destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_U1 => TryFormatPrimitiveNonDefault<byte, byte>(enumType, rawData, destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_I2 => TryFormatPrimitiveNonDefault<short, ushort>(enumType, Unsafe.As<byte, short>(ref rawData), destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_U2 => TryFormatPrimitiveNonDefault<ushort, ushort>(enumType, Unsafe.As<byte, ushort>(ref rawData), destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_I4 => TryFormatPrimitiveNonDefault<int, uint>(enumType, Unsafe.As<byte, int>(ref rawData), destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_U4 => TryFormatPrimitiveNonDefault<uint, uint>(enumType, Unsafe.As<byte, uint>(ref rawData), destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_I8 => TryFormatPrimitiveNonDefault<long, ulong>(enumType, Unsafe.As<byte, long>(ref rawData), destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_U8 => TryFormatPrimitiveNonDefault<ulong, ulong>(enumType, Unsafe.As<byte, ulong>(ref rawData), destination, out charsWritten, format), #if RARE_ENUMS CorElementType.ELEMENT_TYPE_R4 => TryFormatPrimitiveNonDefault<float, float>(enumType, Unsafe.As<byte, float>(ref rawData), destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_R8 => TryFormatPrimitiveNonDefault<double, double>(enumType, Unsafe.As<byte, double>(ref rawData), destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_I => TryFormatPrimitiveNonDefault<nint, nuint>(enumType, Unsafe.As<byte, nint>(ref rawData), destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_U => TryFormatPrimitiveNonDefault<nuint, nuint>(enumType, Unsafe.As<byte, nuint>(ref rawData), destination, out charsWritten, format), CorElementType.ELEMENT_TYPE_CHAR => TryFormatPrimitiveNonDefault<char, char>(enumType, Unsafe.As<byte, char>(ref rawData), destination, out charsWritten, format), #endif _ => throw CreateUnknownEnumTypeException(), }; } } /// <summary>Tries to format the value of the enumerated type instance into the provided span of characters.</summary> /// <typeparam name="TEnum"></typeparam> /// <param name="value"></param> /// <param name="destination">The span into which to write the instance's value formatted as a span of characters.</param> /// <param name="charsWritten">When this method returns, contains the number of characters that were written in <paramref name="destination"/>.</param> /// <param name="format">A span containing the character that represents the standard format string that defines the acceptable format of destination. This may be empty, or "g", "d", "f", or "x".</param> /// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/> if the destination span wasn't large enough to contain the formatted value.</returns> /// <exception cref="FormatException">The format parameter contains an invalid value.</exception> public static unsafe bool TryFormat<TEnum>(TEnum value, Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.EnumFormat)] ReadOnlySpan<char> format = default) where TEnum : struct { RuntimeType rt = (RuntimeType)typeof(TEnum); Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); // If the format is empty, which is the most common case, delegate to the default implementation that doesn't take a format. // That implementation is more optimized. Doing this check here means in the common case where TryFormat is inlined and no format // is provided, this whole call can become just a call to the default method. Even if it's not inlined, this check would still otherwise // be necessary for semantics inside of TryFormatPrimitiveNonDefault, so we can just do it here instead. if (format.IsEmpty) { if (underlyingType == typeof(int)) return TryFormatPrimitiveDefault<int, uint>(rt, *(int*)&value, destination, out charsWritten); if (underlyingType == typeof(uint)) return TryFormatPrimitiveDefault<uint, uint>(rt, *(uint*)&value, destination, out charsWritten); if (underlyingType == typeof(long)) return TryFormatPrimitiveDefault<long, ulong>(rt, *(long*)&value, destination, out charsWritten); if (underlyingType == typeof(ulong)) return TryFormatPrimitiveDefault<ulong, ulong>(rt, *(ulong*)&value, destination, out charsWritten); if (underlyingType == typeof(byte)) return TryFormatPrimitiveDefault<byte, byte>(rt, *(byte*)&value, destination, out charsWritten); if (underlyingType == typeof(sbyte)) return TryFormatPrimitiveDefault<sbyte, byte>(rt, *(sbyte*)&value, destination, out charsWritten); if (underlyingType == typeof(short)) return TryFormatPrimitiveDefault<short, ushort>(rt, *(short*)&value, destination, out charsWritten); if (underlyingType == typeof(ushort)) return TryFormatPrimitiveDefault<ushort, ushort>(rt, *(ushort*)&value, destination, out charsWritten); #if RARE_ENUMS if (underlyingType == typeof(nint)) return TryFormatPrimitiveDefault<nint, nuint>(rt, *(nint*)&value, destination, out charsWritten); if (underlyingType == typeof(nuint)) return TryFormatPrimitiveDefault<nuint, nuint>(rt, *(nuint*)&value, destination, out charsWritten); if (underlyingType == typeof(float)) return TryFormatPrimitiveDefault<float, float>(rt, *(float*)&value, destination, out charsWritten); if (underlyingType == typeof(double)) return TryFormatPrimitiveDefault<double, double>(rt, *(double*)&value, destination, out charsWritten); if (underlyingType == typeof(char)) return TryFormatPrimitiveDefault<char, char>(rt, *(char*)&value, destination, out charsWritten); #endif } else { if (underlyingType == typeof(int)) return TryFormatPrimitiveNonDefault<int, uint>(rt, *(int*)&value, destination, out charsWritten, format); if (underlyingType == typeof(uint)) return TryFormatPrimitiveNonDefault<uint, uint>(rt, *(uint*)&value, destination, out charsWritten, format); if (underlyingType == typeof(long)) return TryFormatPrimitiveNonDefault<long, ulong>(rt, *(long*)&value, destination, out charsWritten, format); if (underlyingType == typeof(ulong)) return TryFormatPrimitiveNonDefault<ulong, ulong>(rt, *(ulong*)&value, destination, out charsWritten, format); if (underlyingType == typeof(byte)) return TryFormatPrimitiveNonDefault<byte, byte>(rt, *(byte*)&value, destination, out charsWritten, format); if (underlyingType == typeof(sbyte)) return TryFormatPrimitiveNonDefault<sbyte, byte>(rt, *(sbyte*)&value, destination, out charsWritten, format); if (underlyingType == typeof(short)) return TryFormatPrimitiveNonDefault<short, ushort>(rt, *(short*)&value, destination, out charsWritten, format); if (underlyingType == typeof(ushort)) return TryFormatPrimitiveNonDefault<ushort, ushort>(rt, *(ushort*)&value, destination, out charsWritten, format); #if RARE_ENUMS if (underlyingType == typeof(nint)) return TryFormatPrimitiveNonDefault<nint, nuint>(rt, *(nint*)&value, destination, out charsWritten, format); if (underlyingType == typeof(nuint)) return TryFormatPrimitiveNonDefault<nuint, nuint>(rt, *(nuint*)&value, destination, out charsWritten, format); if (underlyingType == typeof(float)) return TryFormatPrimitiveNonDefault<float, float>(rt, *(float*)&value, destination, out charsWritten, format); if (underlyingType == typeof(double)) return TryFormatPrimitiveNonDefault<double, double>(rt, *(double*)&value, destination, out charsWritten, format); if (underlyingType == typeof(char)) return TryFormatPrimitiveNonDefault<char, char>(rt, *(char*)&value, destination, out charsWritten, format); #endif } throw CreateUnknownEnumTypeException(); } /// <summary>Tries to format the value of the enumerated type instance into the provided span of characters.</summary> /// <remarks> /// This is same as the implementation for <see cref="TryFormat"/>. It is separated out as <see cref="TryFormat"/> has constrains on the TEnum, /// and we internally want to use this method in cases where we dynamically validate a generic T is an enum rather than T implementing /// those constraints. It's a manual copy/paste right now to avoid pressure on the JIT's inlining mechanisms. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] // format is most frequently a constant, and we want it exposed to the implementation; this should be inlined automatically, anyway internal static unsafe bool TryFormatUnconstrained<TEnum>(TEnum value, Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.EnumFormat)] ReadOnlySpan<char> format = default) { Debug.Assert(typeof(TEnum).IsEnum); Debug.Assert(value is not null); RuntimeType rt = (RuntimeType)typeof(TEnum); Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); // If the format is empty, which is the most common case, delegate to the default implementation that doesn't take a format. // That implementation is more optimized. Doing this check here means in the common case where TryFormat is inlined and no format // is provided, this whole call can become just a call to the default method. Even if it's not inlined, this check would still otherwise // be necessary for semantics inside of TryFormatPrimitiveNonDefault, so we can just do it here instead. if (format.IsEmpty) { if (underlyingType == typeof(int)) return TryFormatPrimitiveDefault<int, uint>(rt, *(int*)&value, destination, out charsWritten); if (underlyingType == typeof(uint)) return TryFormatPrimitiveDefault<uint, uint>(rt, *(uint*)&value, destination, out charsWritten); if (underlyingType == typeof(long)) return TryFormatPrimitiveDefault<long, ulong>(rt, *(long*)&value, destination, out charsWritten); if (underlyingType == typeof(ulong)) return TryFormatPrimitiveDefault<ulong, ulong>(rt, *(ulong*)&value, destination, out charsWritten); if (underlyingType == typeof(byte)) return TryFormatPrimitiveDefault<byte, byte>(rt, *(byte*)&value, destination, out charsWritten); if (underlyingType == typeof(sbyte)) return TryFormatPrimitiveDefault<sbyte, byte>(rt, *(sbyte*)&value, destination, out charsWritten); if (underlyingType == typeof(short)) return TryFormatPrimitiveDefault<short, ushort>(rt, *(short*)&value, destination, out charsWritten); if (underlyingType == typeof(ushort)) return TryFormatPrimitiveDefault<ushort, ushort>(rt, *(ushort*)&value, destination, out charsWritten); #if RARE_ENUMS if (underlyingType == typeof(nint)) return TryFormatPrimitiveDefault<nint, nuint>(rt, *(nint*)&value, destination, out charsWritten); if (underlyingType == typeof(nuint)) return TryFormatPrimitiveDefault<nuint, nuint>(rt, *(nuint*)&value, destination, out charsWritten); if (underlyingType == typeof(float)) return TryFormatPrimitiveDefault<float, float>(rt, *(float*)&value, destination, out charsWritten); if (underlyingType == typeof(double)) return TryFormatPrimitiveDefault<double, double>(rt, *(double*)&value, destination, out charsWritten); if (underlyingType == typeof(char)) return TryFormatPrimitiveDefault<char, char>(rt, *(char*)&value, destination, out charsWritten); #endif } else { if (underlyingType == typeof(int)) return TryFormatPrimitiveNonDefault<int, uint>(rt, *(int*)&value, destination, out charsWritten, format); if (underlyingType == typeof(uint)) return TryFormatPrimitiveNonDefault<uint, uint>(rt, *(uint*)&value, destination, out charsWritten, format); if (underlyingType == typeof(long)) return TryFormatPrimitiveNonDefault<long, ulong>(rt, *(long*)&value, destination, out charsWritten, format); if (underlyingType == typeof(ulong)) return TryFormatPrimitiveNonDefault<ulong, ulong>(rt, *(ulong*)&value, destination, out charsWritten, format); if (underlyingType == typeof(byte)) return TryFormatPrimitiveNonDefault<byte, byte>(rt, *(byte*)&value, destination, out charsWritten, format); if (underlyingType == typeof(sbyte)) return TryFormatPrimitiveNonDefault<sbyte, byte>(rt, *(sbyte*)&value, destination, out charsWritten, format); if (underlyingType == typeof(short)) return TryFormatPrimitiveNonDefault<short, ushort>(rt, *(short*)&value, destination, out charsWritten, format); if (underlyingType == typeof(ushort)) return TryFormatPrimitiveNonDefault<ushort, ushort>(rt, *(ushort*)&value, destination, out charsWritten, format); #if RARE_ENUMS if (underlyingType == typeof(nint)) return TryFormatPrimitiveNonDefault<nint, nuint>(rt, *(nint*)&value, destination, out charsWritten, format); if (underlyingType == typeof(nuint)) return TryFormatPrimitiveNonDefault<nuint, nuint>(rt, *(nuint*)&value, destination, out charsWritten, format); if (underlyingType == typeof(float)) return TryFormatPrimitiveNonDefault<float, float>(rt, *(float*)&value, destination, out charsWritten, format); if (underlyingType == typeof(double)) return TryFormatPrimitiveNonDefault<double, double>(rt, *(double*)&value, destination, out charsWritten, format); if (underlyingType == typeof(char)) return TryFormatPrimitiveNonDefault<char, char>(rt, *(char*)&value, destination, out charsWritten, format); #endif } throw CreateUnknownEnumTypeException(); } /// <summary>Core implementation for <see cref="TryFormat"/> when no format specifier was provided.</summary> private static bool TryFormatPrimitiveDefault<TUnderlying, TStorage>(RuntimeType enumType, TUnderlying value, Span<char> destination, out int charsWritten) where TUnderlying : struct, INumber<TUnderlying>, IBitwiseOperators<TUnderlying, TUnderlying, TUnderlying>, IMinMaxValue<TUnderlying> where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage>, IMinMaxValue<TStorage> { AssertValidGenerics<TUnderlying, TStorage>(); EnumInfo<TStorage> enumInfo = GetEnumInfo<TStorage>(enumType); if (!enumInfo.HasFlagsAttribute) { if (GetNameInlined(enumInfo, Unsafe.BitCast<TUnderlying, TStorage>(value)) is string enumName) { if (enumName.TryCopyTo(destination)) { charsWritten = enumName.Length; return true; } charsWritten = 0; return false; } } else { bool destinationIsTooSmall = false; if (TryFormatFlagNames(enumInfo, Unsafe.BitCast<TUnderlying, TStorage>(value), destination, out charsWritten, ref destinationIsTooSmall) || destinationIsTooSmall) { return !destinationIsTooSmall; } } return value.TryFormat(destination, out charsWritten, format: default, provider: null); } /// <summary>Core implementation for <see cref="TryFormat"/> when a format specifier was provided.</summary> private static bool TryFormatPrimitiveNonDefault<TUnderlying, TStorage>(RuntimeType enumType, TUnderlying value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format) where TUnderlying : struct, INumber<TUnderlying>, IBitwiseOperators<TUnderlying, TUnderlying, TUnderlying>, IMinMaxValue<TUnderlying> where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage>, IMinMaxValue<TStorage> { AssertValidGenerics<TUnderlying, TStorage>(); Debug.Assert(!format.IsEmpty); if (format.Length == 1) { switch (format[0] | 0x20) { case 'g': return TryFormatPrimitiveDefault<TUnderlying, TStorage>(enumType, value, destination, out charsWritten); case 'd': return value.TryFormat(destination, out charsWritten, format: default, provider: null); case 'x': return TryFormatNumberAsHex<TStorage>(ref Unsafe.As<TUnderlying, byte>(ref value), destination, out charsWritten); case 'f': bool destinationIsTooSmall = false; if (TryFormatFlagNames(GetEnumInfo<TStorage>(enumType), Unsafe.BitCast<TUnderlying, TStorage>(value), destination, out charsWritten, ref destinationIsTooSmall) || destinationIsTooSmall) { return !destinationIsTooSmall; } goto case 'd'; } } throw CreateInvalidFormatSpecifierException(); } /// <summary>Tries to create a string representation of an enum as either a single constant name or multiple delimited constant names.</summary> /// <returns>The formatted string if the value could be fully represented by enum constants, or else null.</returns> private static unsafe string? FormatFlagNames<TStorage>(EnumInfo<TStorage> enumInfo, TStorage resultValue) where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage> { string[] names = enumInfo.Names; TStorage[] values = enumInfo.Values; Debug.Assert(names.Length == values.Length); string? result = GetSingleFlagsEnumNameForValue(resultValue, names, values, out int index); if (result is null) { // With a ulong result value, regardless of the enum's base type, the maximum // possible number of consistent name/values we could have is 64, since every // value is made up of one or more bits, and when we see values and incorporate // their names, we effectively switch off those bits. Span<int> foundItems = stackalloc int[64]; if (TryFindFlagsNames(resultValue, names, values, index, foundItems, out int resultLength, out int foundItemsCount)) { foundItems = foundItems.Slice(0, foundItemsCount); int length = GetMultipleEnumsFlagsFormatResultLength(resultLength, foundItemsCount); result = string.FastAllocateString(length); WriteMultipleFoundFlagsNames(names, foundItems, new Span<char>(ref result.GetRawStringData(), result.Length)); } } return result; } /// <summary>Tries to format into a span a representation of an enum as either a single constant name or multiple delimited constant names.</summary> /// <returns> /// true if the value could be fully represented by enum constants and if the formatted value could fit into the destination span; otherwise, false. /// If false, <paramref name="isDestinationTooSmall"/> is used to disambiguate the reason for the failure. /// </returns> private static unsafe bool TryFormatFlagNames<TStorage>(EnumInfo<TStorage> enumInfo, TStorage resultValue, Span<char> destination, out int charsWritten, ref bool isDestinationTooSmall) where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage> { Debug.Assert(!isDestinationTooSmall); string[] names = enumInfo.Names; TStorage[] values = enumInfo.Values; Debug.Assert(names.Length == values.Length); if (GetSingleFlagsEnumNameForValue(resultValue, names, values, out int index) is string singleEnumFlagsFormat) { if (singleEnumFlagsFormat.TryCopyTo(destination)) { charsWritten = singleEnumFlagsFormat.Length; return true; } isDestinationTooSmall = true; } else { // With a ulong result value, regardless of the enum's base type, the maximum // possible number of consistent name/values we could have is 64, since every // value is made up of one or more bits, and when we see values and incorporate // their names, we effectively switch off those bits. Span<int> foundItems = stackalloc int[64]; if (TryFindFlagsNames(resultValue, names, values, index, foundItems, out int resultLength, out int foundItemsCount)) { foundItems = foundItems.Slice(0, foundItemsCount); int length = GetMultipleEnumsFlagsFormatResultLength(resultLength, foundItemsCount); if (length <= destination.Length) { charsWritten = length; WriteMultipleFoundFlagsNames(names, foundItems, destination); return true; } isDestinationTooSmall = true; } } charsWritten = 0; return false; } /// <summary> /// Calculates how many characters will be in a formatted value, where there are <paramref name="foundItemsCount"/> /// names whose lengths all sum to <paramref name="resultLength"/>. /// </summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] // used twice, once from string-based and once from span-based code path private static int GetMultipleEnumsFlagsFormatResultLength(int resultLength, int foundItemsCount) { Debug.Assert(foundItemsCount >= 2 && foundItemsCount <= 64, $"{nameof(foundItemsCount)} == {foundItemsCount}"); const int SeparatorStringLength = 2; // ", " int allSeparatorsLength = SeparatorStringLength * (foundItemsCount - 1); // this can't overflow return checked(resultLength + allSeparatorsLength); } /// <summary>Tries to find the single named constant for the specified value, or else the index where we left off searching after not finding it.</summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] // used twice, once from string-based and once from span-based code path private static string? GetSingleFlagsEnumNameForValue<TStorage>(TStorage resultValue, string[] names, TStorage[] values, out int index) where TStorage : struct, INumber<TStorage> { // Values are sorted, so if the incoming value is 0, we can check to see whether // the first entry matches it, in which case we can return its name; otherwise, // we can just return "0". if (resultValue == TStorage.Zero) { index = 0; return values.Length > 0 && values[0] == TStorage.Zero ? names[0] : "0"; } // Walk from largest to smallest. It's common to have a flags enum with a single // value that matches a single entry, in which case we can just return the existing // name string. int i; for (i = values.Length - 1; (uint)i < (uint)values.Length; i--) { if (values[i] <= resultValue) { if (values[i] == resultValue) { index = i; return names[i]; } break; } } index = i; return null; } /// <summary>Tries to compute the indices of all named constants that or together to equal the specified value.</summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] // used twice, once from string-based and once from span-based code path private static bool TryFindFlagsNames<TStorage>(TStorage resultValue, string[] names, TStorage[] values, int index, Span<int> foundItems, out int resultLength, out int foundItemsCount) where TStorage : struct, INumber<TStorage>, IBitwiseOperators<TStorage, TStorage, TStorage> { // Now look for multiple matches, storing the indices of the values // into our span. resultLength = 0; foundItemsCount = 0; while (true) { if ((uint)index >= (uint)values.Length) { break; } TStorage currentValue = values[index]; if (index == 0 && currentValue == TStorage.Zero) { break; } if ((resultValue & currentValue) == currentValue) { resultValue &= ~currentValue; foundItems[foundItemsCount] = index; foundItemsCount++; resultLength = checked(resultLength + names[index].Length); if (resultValue == TStorage.Zero) { break; } } index--; } // If we exhausted looking through all the values and we still have // a non-zero result, we couldn't match the result to only named values. // In that case, we return null and let the call site just generate // a string for the integral value if it desires. return resultValue == TStorage.Zero; } /// <summary>Concatenates the names of the found items into the destination span.</summary> /// <remarks>The destination must have already been verified long enough to store the resulting data.</remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] // used twice, once from string-based and once from span-based code path private static void WriteMultipleFoundFlagsNames(string[] names, ReadOnlySpan<int> foundItems, Span<char> destination) { Debug.Assert(foundItems.Length >= 2, $"{nameof(foundItems)} == {foundItems.Length}"); for (int i = foundItems.Length - 1; i != 0; i--) { string name = names[foundItems[i]]; name.CopyTo(destination); destination = destination.Slice(name.Length); Span<char> afterSeparator = destination.Slice(2); // done before copying ", " to eliminate those two bounds checks destination[0] = EnumSeparatorChar; destination[1] = ' '; destination = afterSeparator; } names[foundItems[0]].CopyTo(destination); } private static RuntimeType ValidateRuntimeType(Type enumType) { ArgumentNullException.ThrowIfNull(enumType); RuntimeType? rt = enumType as RuntimeType; if (rt is null || !rt.IsActualEnum) { ThrowInvalidRuntimeType(enumType); } #if NATIVEAOT // Check for the unfortunate "typeof(Outer<>.InnerEnum)" corner case. // https://github.com/dotnet/runtime/issues/7976 if (rt.ContainsGenericParameters) throw new InvalidOperationException(SR.Format(SR.Arg_OpenType, rt.ToString())); #endif return rt; } [DoesNotReturn] private static void ThrowInvalidRuntimeType(Type enumType) => throw new ArgumentException(enumType is not RuntimeType ? SR.Arg_MustBeType : SR.Arg_MustBeEnum, nameof(enumType)); private static void ThrowInvalidEmptyParseArgument() => throw new ArgumentException(SR.Arg_MustContainEnumInfo, "value"); [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/78300 private static FormatException CreateInvalidFormatSpecifierException() => new FormatException(SR.Format_InvalidEnumFormatSpecification); [MethodImpl(MethodImplOptions.NoInlining)] private static InvalidOperationException CreateUnknownEnumTypeException() => new InvalidOperationException(SR.InvalidOperation_UnknownEnumType); public TypeCode GetTypeCode() => InternalGetCorElementType() switch { CorElementType.ELEMENT_TYPE_I1 => TypeCode.SByte, CorElementType.ELEMENT_TYPE_U1 => TypeCode.Byte, CorElementType.ELEMENT_TYPE_I2 => TypeCode.Int16, CorElementType.ELEMENT_TYPE_U2 => TypeCode.UInt16, CorElementType.ELEMENT_TYPE_I4 => TypeCode.Int32, CorElementType.ELEMENT_TYPE_U4 => TypeCode.UInt32, CorElementType.ELEMENT_TYPE_I8 => TypeCode.Int64, CorElementType.ELEMENT_TYPE_U8 => TypeCode.UInt64, CorElementType.ELEMENT_TYPE_CHAR => TypeCode.Char, // There's no TypeCode for nint or nuint, and our VB support (or at least // tests) needs to be updated in order to include float/double here. _ => throw CreateUnknownEnumTypeException(), }; bool IConvertible.ToBoolean(IFormatProvider? provider) => Convert.ToBoolean(GetValue()); char IConvertible.ToChar(IFormatProvider? provider) => Convert.ToChar(GetValue()); sbyte IConvertible.ToSByte(IFormatProvider? provider) => Convert.ToSByte(GetValue()); byte IConvertible.ToByte(IFormatProvider? provider) => Convert.ToByte(GetValue()); short IConvertible.ToInt16(IFormatProvider? provider) => Convert.ToInt16(GetValue()); ushort IConvertible.ToUInt16(IFormatProvider? provider) => Convert.ToUInt16(GetValue()); int IConvertible.ToInt32(IFormatProvider? provider) => Convert.ToInt32(GetValue()); uint IConvertible.ToUInt32(IFormatProvider? provider) => Convert.ToUInt32(GetValue()); long IConvertible.ToInt64(IFormatProvider? provider) => Convert.ToInt64(GetValue()); ulong IConvertible.ToUInt64(IFormatProvider? provider) => Convert.ToUInt64(GetValue()); float IConvertible.ToSingle(IFormatProvider? provider) => Convert.ToSingle(GetValue()); double IConvertible.ToDouble(IFormatProvider? provider) => Convert.ToDouble(GetValue()); decimal IConvertible.ToDecimal(IFormatProvider? provider) => Convert.ToDecimal(GetValue()); DateTime IConvertible.ToDateTime(IFormatProvider? provider) => throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Enum", "DateTime")); object IConvertible.ToType(Type type, IFormatProvider? provider) => Convert.DefaultToType(this, type, provider); public static object ToObject(Type enumType, object value) { ArgumentNullException.ThrowIfNull(value); switch (Convert.GetTypeCode(value)) { case TypeCode.Int32: return ToObject(enumType, (int)value); case TypeCode.SByte: return ToObject(enumType, (sbyte)value); case TypeCode.Int16: return ToObject(enumType, (short)value); case TypeCode.Int64: return ToObject(enumType, (long)value); case TypeCode.UInt32: return ToObject(enumType, (uint)value); case TypeCode.Byte: return ToObject(enumType, (byte)value); case TypeCode.UInt16: return ToObject(enumType, (ushort)value); case TypeCode.UInt64: return ToObject(enumType, (ulong)value); case TypeCode.Char: return ToObject(enumType, (char)value); case TypeCode.Boolean: return ToObject(enumType, (bool)value ? 1L : 0L); }; Type valueType = value.GetType(); if (valueType.IsEnum) { valueType = valueType.GetEnumUnderlyingType(); } if (valueType == typeof(nint)) return ToObject(enumType, (nint)value); if (valueType == typeof(nuint)) return ToObject(enumType, (nuint)value); throw new ArgumentException(SR.Arg_MustBeEnumBaseTypeOrEnum, nameof(value)); } [CLSCompliant(false)] public static object ToObject(Type enumType, sbyte value) => InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value); public static object ToObject(Type enumType, short value) => InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value); public static object ToObject(Type enumType, int value) => InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value); public static object ToObject(Type enumType, byte value) => InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value); [CLSCompliant(false)] public static object ToObject(Type enumType, ushort value) => InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value); [CLSCompliant(false)] public static object ToObject(Type enumType, uint value) => InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value); public static object ToObject(Type enumType, long value) => InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value); [CLSCompliant(false)] public static object ToObject(Type enumType, ulong value) => InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, unchecked((long)value)); private static object InternalBoxEnum(RuntimeTypeHandle type, long value) { ReadOnlySpan<byte> rawData = MemoryMarshal.AsBytes(new ReadOnlySpan<long>(ref value)); // On little-endian systems, we can always use the pointer to the start of the scratch space // as memory layout since the least-significant bit is at the lowest address. // For big-endian systems, the least-significant bit is at the highest address, so we need to adjust // our starting ref to the correct offset from the end of the scratch space to get the value to box. if (!BitConverter.IsLittleEndian) { int size = RuntimeHelpers.SizeOf(type); rawData = rawData.Slice(sizeof(long) - size); } return RuntimeHelpers.Box(ref MemoryMarshal.GetReference(rawData), type)!; } internal static bool AreSequentialFromZero<TStorage>(TStorage[] values) where TStorage : struct, INumber<TStorage> { for (int i = 0; i < values.Length; i++) { if (ulong.CreateTruncating(values[i]) != (ulong)i) { return false; } } return true; } internal static bool AreSorted<TStorage>(TStorage[] values) where TStorage : struct, IComparable<TStorage> { for (int i = 1; i < values.Length; i++) { if (values[i - 1].CompareTo(values[i]) > 0) { return false; } } return true; } [Conditional("DEBUG")] private static void AssertValidGenerics<TUnderlying, TStorage>() { Debug.Assert( typeof(TUnderlying) == typeof(sbyte) || typeof(TUnderlying) == typeof(byte) || typeof(TUnderlying) == typeof(short) || typeof(TUnderlying) == typeof(ushort) || typeof(TUnderlying) == typeof(int) || typeof(TUnderlying) == typeof(uint) || typeof(TUnderlying) == typeof(long) || typeof(TUnderlying) == typeof(ulong) || typeof(TUnderlying) == typeof(nint) || typeof(TUnderlying) == typeof(nuint) || typeof(TUnderlying) == typeof(float) || typeof(TUnderlying) == typeof(double) || typeof(TUnderlying) == typeof(char)); if (typeof(TUnderlying) == typeof(sbyte)) Debug.Assert(typeof(TStorage) == typeof(byte)); else if (typeof(TUnderlying) == typeof(short)) Debug.Assert(typeof(TStorage) == typeof(ushort)); else if (typeof(TUnderlying) == typeof(int)) Debug.Assert(typeof(TStorage) == typeof(uint)); else if (typeof(TUnderlying) == typeof(long)) Debug.Assert(typeof(TStorage) == typeof(ulong)); else if (typeof(TUnderlying) == typeof(nint)) Debug.Assert(typeof(TStorage) == typeof(nuint)); else Debug.Assert(typeof(TUnderlying) == typeof(TStorage)); } } }