From 62f3eb2878c3e82631b91dd02ee4fb655d23a2b1 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 5 Dec 2022 22:37:30 -0500 Subject: [PATCH] Rewrite Enum and add {ISpanFormattable}.TryFormat (#78580) * Add generic Enum.TryFormat method * Rewrite Enum using generic specialization per underlying type Also revises the shape of the newly added Enum.TryFormat method and fixes tests accordingly. * Use Enum.TryFormat in interpolated string handlers * Implement ISpanFormattable on Enum * Use generic Enum methods in more places * Replace Unsafe.As with unmanaged pointers where possible Reduces cost of the generic methods * Simplify Enum_GetValuesAndNames QCall, fix handling of floats and doubles This change avoids allocation of the temporary ulong[] array during EnumInfo initialization Fixes #29266 * Fix mono's GetEnumNamesAndValues, and make ToObject handle float/double as well * Fix perf gap and polish * Undo update of Enum.GetTypeCode to include float/double * Address PR feedback * Accomodate net8.0 TFM update * Fix InvalidCastException from GetEnumValues on mono when dealing with bool-based enums Co-authored-by: Heath Baron-Morgan Co-authored-by: Jan Kotas --- .../src/System/Enum.CoreCLR.cs | 30 +- .../src/CompatibilitySuppressions.xml | 6 +- .../Reflection/Augments/ReflectionAugments.cs | 4 +- .../Core/Execution/ExecutionEnvironment.cs | 5 +- .../src/System/Enum.NativeAot.cs | 40 +- .../src/System/Reflection/EnumInfo.cs | 86 +- .../ReflectionCoreCallbacksImplementation.cs | 6 +- .../src/System/RuntimeType.cs | 22 +- .../ReflectionCoreCallbacksImplementation.cs | 8 +- ...cutionEnvironmentImplementation.Runtime.cs | 8 +- .../Execution/NativeFormatEnumInfo.cs | 10 +- src/coreclr/vm/reflectioninvocation.cpp | 129 +- .../RuntimeBinder/Semantics/Operators.cs | 6 +- .../src/Microsoft/Win32/RegistryKey.cs | 2 +- .../tests/EnumConverterTest.cs | 2 +- .../src/System/Diagnostics/Process.cs | 2 +- .../System/Diagnostics/ProcessStartInfo.cs | 2 +- .../Drawing/Printing/PrinterSettings.cs | 2 +- .../src/System/Boolean.cs | 1 - .../src/System/Enum.EnumInfo.cs | 26 +- .../System.Private.CoreLib/src/System/Enum.cs | 2479 +++++++++++------ .../src/System/Environment.cs | 4 +- .../src/System/MemoryExtensions.cs | 30 +- .../NeutralResourcesLanguageAttribute.cs | 2 +- .../DefaultInterpolatedStringHandler.cs | 27 + .../src/System/RuntimeType.cs | 172 +- .../src/System/Text/StringBuilder.cs | 28 +- .../Text/ValueStringBuilder.AppendFormat.cs | 4 +- .../src/System/ThrowHelper.cs | 4 +- .../System.Private.Uri/src/System/UriExt.cs | 4 - .../System/Xml/Xsl/IlGen/OptimizerPatterns.cs | 4 +- .../System.Runtime/ref/System.Runtime.cs | 5 +- .../System.Runtime/tests/System/EnumTests.cs | 455 ++- .../ServiceProcess/ServiceController.cs | 2 +- .../src/System/Enum.Mono.cs | 71 +- .../src/System/RuntimeType.Mono.cs | 2 +- src/mono/mono/metadata/icall-def.h | 2 +- src/mono/mono/metadata/icall.c | 24 +- 38 files changed, 2361 insertions(+), 1355 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs index 18d9a6db2064b..c6aac1b7bfb96 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; +using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -20,6 +20,15 @@ public abstract partial class Enum [MethodImpl(MethodImplOptions.InternalCall)] private static extern unsafe CorElementType InternalGetCorElementType(MethodTable* pMT); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe CorElementType InternalGetCorElementType(RuntimeType rt) + { + Debug.Assert(rt.IsActualEnum); + CorElementType elementType = InternalGetCorElementType((MethodTable*)rt.GetUnderlyingNativeHandle()); + GC.KeepAlive(rt); + return elementType; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe CorElementType InternalGetCorElementType() { @@ -73,26 +82,31 @@ internal static unsafe RuntimeType InternalGetUnderlyingType(RuntimeType enumTyp } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static EnumInfo GetEnumInfo(RuntimeType enumType, bool getNames = true) + private static EnumInfo GetEnumInfo(RuntimeType enumType, bool getNames = true) + where TUnderlyingValue : struct, INumber { - return enumType.GenericCache is EnumInfo info && (!getNames || info.Names is not null) ? + return enumType.GenericCache is EnumInfo info && (!getNames || info.Names is not null) ? info : InitializeEnumInfo(enumType, getNames); [MethodImpl(MethodImplOptions.NoInlining)] - static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames) + static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames) { - ulong[]? values = null; + TUnderlyingValue[]? values = null; string[]? names = null; - RuntimeTypeHandle enumTypeHandle = enumType.TypeHandle; + GetEnumValuesAndNames( - new QCallTypeHandle(ref enumTypeHandle), + new QCallTypeHandle(ref enumType), ObjectHandleOnStack.Create(ref values), ObjectHandleOnStack.Create(ref names), getNames ? Interop.BOOL.TRUE : Interop.BOOL.FALSE); + + Debug.Assert(values!.GetType() == typeof(TUnderlyingValue[])); + Debug.Assert(!getNames || names!.GetType() == typeof(string[])); + bool hasFlagsAttribute = enumType.IsDefined(typeof(FlagsAttribute), inherit: false); - var entry = new EnumInfo(hasFlagsAttribute, values!, names!); + var entry = new EnumInfo(hasFlagsAttribute, values, names!); enumType.GenericCache = entry; return entry; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 1c3325d92a44d..529449f22e2ba 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -824,6 +824,10 @@ CP0001 T:System.Reflection.EnumInfo + + CP0001 + T:System.Reflection.EnumInfo`1 + CP0001 T:System.Reflection.Runtime.General.MetadataReaderExtensions @@ -972,4 +976,4 @@ CP0016 M:System.Runtime.InteropServices.Marshal.GetObjectForNativeVariant``1(System.IntPtr)->T?:[T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute] - \ No newline at end of file + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Augments/ReflectionAugments.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Augments/ReflectionAugments.cs index 7c3ac45f9ffca..48e00e07c6674 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Augments/ReflectionAugments.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Augments/ReflectionAugments.cs @@ -21,6 +21,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Numerics; using EETypeElementType = Internal.Runtime.EETypeElementType; @@ -164,7 +165,8 @@ public abstract object ActivatorCreateInstance( public abstract Assembly[] GetLoadedAssemblies(); - public abstract EnumInfo GetEnumInfo(Type type); + public abstract EnumInfo GetEnumInfo(Type type) + where TUnderlyingValue : struct, INumber; public abstract DynamicInvokeInfo GetDelegateDynamicInvokeInfo(Type type); } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs index 750a56ea13d56..61713b1c654a4 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionEnvironment.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Numerics; using System.Reflection.Runtime.General; using System.Reflection.Runtime.TypeInfos; using System.Runtime.CompilerServices; @@ -97,7 +98,9 @@ public abstract class ExecutionEnvironment // Other //============================================================================================== public abstract FieldAccessor CreateLiteralFieldAccessor(object value, RuntimeTypeHandle fieldTypeHandle); - public abstract EnumInfo GetEnumInfo(RuntimeTypeHandle typeHandle); + public abstract EnumInfo GetEnumInfo(RuntimeTypeHandle typeHandle) + where TUnderlyingValue : struct, INumber; + public abstract IntPtr GetDynamicInvokeThunk(MethodInvoker invoker); //============================================================================================== diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Enum.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Enum.NativeAot.cs index 99787cad583fc..6bdb4725be726 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Enum.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Enum.NativeAot.cs @@ -3,9 +3,11 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Numerics; using System.Reflection; using System.Runtime; using System.Runtime.CompilerServices; +using Internal.Runtime.Augments; using Internal.Runtime.CompilerServices; using Internal.Reflection.Augments; @@ -23,7 +25,29 @@ internal static EnumInfo GetEnumInfo(Type enumType, bool getNames = true) Debug.Assert(enumType is RuntimeType); Debug.Assert(enumType.IsEnum); - return ReflectionAugments.ReflectionCoreCallbacks.GetEnumInfo(enumType); + RuntimeType rt = (RuntimeType)enumType; + return Type.GetTypeCode(RuntimeAugments.GetEnumUnderlyingType(rt.TypeHandle)) switch + { + TypeCode.SByte => GetEnumInfo(rt), + TypeCode.Byte => GetEnumInfo(rt), + TypeCode.Int16 => GetEnumInfo(rt), + TypeCode.UInt16 => GetEnumInfo(rt), + TypeCode.Int32 => GetEnumInfo(rt), + TypeCode.UInt32 => GetEnumInfo(rt), + TypeCode.Int64 => GetEnumInfo(rt), + TypeCode.UInt64 => GetEnumInfo(rt), + _ => throw new NotSupportedException(), + }; + } + + internal static EnumInfo GetEnumInfo(Type enumType, bool getNames = true) + where TUnderlyingValue : struct, INumber + { + Debug.Assert(enumType != null); + Debug.Assert(enumType is RuntimeType); + Debug.Assert(enumType.IsEnum); + + return ReflectionAugments.ReflectionCoreCallbacks.GetEnumInfo(enumType); } #pragma warning restore @@ -32,6 +56,12 @@ private static object InternalBoxEnum(Type enumType, long value) return ToObject(enumType.TypeHandle.ToEETypePtr(), value); } + private static CorElementType InternalGetCorElementType(RuntimeType rt) + { + Debug.Assert(rt.IsActualEnum); + return rt.TypeHandle.ToEETypePtr().CorElementType; + } + private CorElementType InternalGetCorElementType() { return this.GetEETypePtr().CorElementType; @@ -115,14 +145,6 @@ internal static Type InternalGetUnderlyingType(RuntimeType enumType) return GetEnumInfo(enumType).UnderlyingType; } - public static TEnum[] GetValues() where TEnum : struct, Enum - { - Array values = GetEnumInfo(typeof(TEnum)).ValuesAsUnderlyingType; - TEnum[] result = new TEnum[values.Length]; - Array.Copy(values, result, values.Length); - return result; - } - // // Checks if value.GetType() matches enumType exactly. // diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/EnumInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/EnumInfo.cs index 87509675a9697..5afba6812ffcc 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/EnumInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/EnumInfo.cs @@ -4,84 +4,46 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Numerics; using System.Runtime; using System.Runtime.CompilerServices; namespace System.Reflection { [ReflectionBlocked] - public sealed class EnumInfo + public abstract class EnumInfo { - public EnumInfo(Type underlyingType, object[] rawValues, string[] names, bool isFlags) + private protected EnumInfo(Type underlyingType, string[] names, bool isFlags) { - Debug.Assert(rawValues.Length == names.Length); - UnderlyingType = underlyingType; - - int numValues = rawValues.Length; - ulong[] values = new ulong[numValues]; - for (int i = 0; i < numValues; i++) - { - object rawValue = rawValues[i]; - - ulong rawUnboxedValue; - if (rawValue is ulong) - { - rawUnboxedValue = (ulong)rawValue; - } - else - { - // This conversion is this way for compatibility: do a value-preseving cast to long - then store (and compare) as ulong. This affects - // the order in which the Enum apis return names and values. - rawUnboxedValue = (ulong)(((IConvertible)rawValue).ToInt64(null)); - } - values[i] = rawUnboxedValue; - } - - // Need to sort the `names` and `rawValues` arrays according to the `values` array - ulong[] valuesCopy = (ulong[])values.Clone(); - Array.Sort(keys: valuesCopy, items: rawValues, comparer: Comparer.Default); - Array.Sort(keys: values, items: names, comparer: Comparer.Default); - Names = names; - Values = values; - - // Create the unboxed version of values for the Values property to return. (We didn't do this earlier because - // declaring "rawValues" as "Array" would prevent us from using the generic overload of Array.Sort()). - // - // The array element type is the underlying type, not the enum type. (The enum type could be an open generic.) - ValuesAsUnderlyingType = Type.GetTypeCode(UnderlyingType) switch - { - TypeCode.Byte => new byte[numValues], - TypeCode.SByte => new sbyte[numValues], - TypeCode.UInt16 => new ushort[numValues], - TypeCode.Int16 => new short[numValues], - TypeCode.UInt32 => new uint[numValues], - TypeCode.Int32 => new int[numValues], - TypeCode.UInt64 => new ulong[numValues], - TypeCode.Int64 => new long[numValues], - _ => throw new NotSupportedException(), - }; - Array.Copy(rawValues, ValuesAsUnderlyingType, numValues); - HasFlagsAttribute = isFlags; - - ValuesAreSequentialFromZero = true; - for (int i = 0; i < values.Length; i++) - { - if (values[i] != (ulong)i) - { - ValuesAreSequentialFromZero = false; - break; - } - } } internal Type UnderlyingType { get; } internal string[] Names { get; } - internal ulong[] Values { get; } - internal Array ValuesAsUnderlyingType { get; } internal bool HasFlagsAttribute { get; } + } + + [ReflectionBlocked] + public sealed class EnumInfo : EnumInfo + where TUnderlyingValue : struct, INumber + { + public EnumInfo(Type underlyingType, TUnderlyingValue[] values, string[] names, bool isFlags) : + base(underlyingType, names, isFlags) + { + Debug.Assert(values.Length == names.Length); + + Array.Sort(keys: values, items: names); + + Values = values; + ValuesAreSequentialFromZero = Enum.AreSequentialFromZero(values); + } + + internal TUnderlyingValue[] Values { get; } internal bool ValuesAreSequentialFromZero { get; } + + public TUnderlyingValue[] CloneValues() => + new ReadOnlySpan(Values).ToArray(); } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs index 17455b1f5ec56..53f932b4f99d3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ReflectionCoreCallbacksImplementation.cs @@ -404,15 +404,15 @@ public sealed override void MakeTypedReference(object target, FieldInfo[] flds, public sealed override Assembly[] GetLoadedAssemblies() => RuntimeAssemblyInfo.GetLoadedAssemblies(); - public sealed override EnumInfo GetEnumInfo(Type type) + public sealed override EnumInfo GetEnumInfo(Type type) { RuntimeTypeInfo runtimeType = type.CastToRuntimeTypeInfo(); - EnumInfo? info = runtimeType.GenericCache as EnumInfo; + EnumInfo? info = runtimeType.GenericCache as EnumInfo; if (info != null) return info; - info = ReflectionCoreExecution.ExecutionDomain.ExecutionEnvironment.GetEnumInfo(runtimeType.TypeHandle); + info = ReflectionCoreExecution.ExecutionDomain.ExecutionEnvironment.GetEnumInfo(runtimeType.TypeHandle); runtimeType.GenericCache = info; return info; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs index 0422e7ba82e31..a5cec81bb97e0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs @@ -22,9 +22,9 @@ public abstract class RuntimeType : TypeInfo // Do a value-preserving cast of both it and the enum values and do a 64-bit compare. if (!IsActualEnum) - throw new ArgumentException(SR.Arg_MustBeEnum); + throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); - return Enum.GetEnumName(this, rawValue); + return Enum.GetName(this, rawValue); } public sealed override string[] GetEnumNames() @@ -32,7 +32,7 @@ public sealed override string[] GetEnumNames() if (!IsActualEnum) throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); - string[] ret = Enum.InternalGetNames(this); + string[] ret = Enum.GetNamesNoCopy(this); // Make a copy since we can't hand out the same array since users can modify them return new ReadOnlySpan(ret).ToArray(); @@ -87,7 +87,7 @@ public sealed override bool IsEnumDefined(object value) throw new ArgumentException(SR.Format(SR.Arg_EnumUnderlyingTypeAndObjectMustBeSameType, value.GetType(), underlyingType)); } - return Enum.GetEnumName(this, rawValue) != null; + return Enum.GetName(this, rawValue) != null; } } @@ -97,16 +97,16 @@ public sealed override Array GetEnumValues() if (!IsActualEnum) throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); - Array values = Enum.GetEnumInfo(this).ValuesAsUnderlyingType; + Array values = Enum.GetValuesAsUnderlyingTypeNoCopy(this); int count = values.Length; + // Without universal shared generics, chances are slim that we'll have the appropriate // array type available. Offer an escape hatch that avoids a missing metadata exception // at the cost of a small appcompat risk. - Array result; - if (AppContext.TryGetSwitch("Switch.System.Enum.RelaxedGetValues", out bool isRelaxed) && isRelaxed) - result = Array.CreateInstance(Enum.InternalGetUnderlyingType(this), count); - else - result = Array.CreateInstance(this, count); + Array result = AppContext.TryGetSwitch("Switch.System.Enum.RelaxedGetValues", out bool isRelaxed) && isRelaxed ? + Array.CreateInstance(Enum.InternalGetUnderlyingType(this), count) : + Array.CreateInstance(this, count); + Array.Copy(values, result, values.Length); return result; } @@ -116,7 +116,7 @@ public sealed override Array GetEnumValuesAsUnderlyingType() if (!IsActualEnum) throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); - return (Array)Enum.GetEnumInfo(this).ValuesAsUnderlyingType.Clone(); + return (Array)Enum.GetValuesAsUnderlyingTypeNoCopy(this).Clone(); } internal bool IsActualEnum diff --git a/src/coreclr/nativeaot/System.Private.DisabledReflection/src/Internal/Reflection/ReflectionCoreCallbacksImplementation.cs b/src/coreclr/nativeaot/System.Private.DisabledReflection/src/Internal/Reflection/ReflectionCoreCallbacksImplementation.cs index 60de9603f48b2..74436dccf1f0a 100644 --- a/src/coreclr/nativeaot/System.Private.DisabledReflection/src/Internal/Reflection/ReflectionCoreCallbacksImplementation.cs +++ b/src/coreclr/nativeaot/System.Private.DisabledReflection/src/Internal/Reflection/ReflectionCoreCallbacksImplementation.cs @@ -12,14 +12,12 @@ namespace Internal.Reflection { internal class ReflectionCoreCallbacksImplementation : ReflectionCoreCallbacks { - public override EnumInfo GetEnumInfo(Type type) - { - return new EnumInfo( + public override EnumInfo GetEnumInfo(Type type) => + new EnumInfo( RuntimeAugments.GetEnumUnderlyingType(type.TypeHandle), - rawValues: Array.Empty(), + values: Array.Empty(), names: Array.Empty(), isFlags: false); - } public override DynamicInvokeInfo GetDelegateDynamicInvokeInfo(Type type) => throw new NotSupportedException(SR.Reflection_Disabled); diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs index f221221432f63..5abd1bc94521b 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.Runtime.cs @@ -128,7 +128,7 @@ public sealed override FieldAccessor CreateLiteralFieldAccessor(object value, Ru return new LiteralFieldAccessor(value, fieldTypeHandle); } - public sealed override EnumInfo GetEnumInfo(RuntimeTypeHandle typeHandle) + public sealed override EnumInfo GetEnumInfo(RuntimeTypeHandle typeHandle) { // Handle the weird case of an enum type nested under a generic type that makes the // enum itself generic @@ -141,7 +141,7 @@ public sealed override EnumInfo GetEnumInfo(RuntimeTypeHandle typeHandle) // If the type is reflection blocked, we pretend there are no enum values defined if (ReflectionExecution.ExecutionEnvironment.IsReflectionBlocked(typeDefHandle)) { - return new EnumInfo(RuntimeAugments.GetEnumUnderlyingType(typeHandle), Array.Empty(), Array.Empty(), false); + return new EnumInfo(RuntimeAugments.GetEnumUnderlyingType(typeHandle), Array.Empty(), Array.Empty(), false); } QTypeDefinition qTypeDefinition; @@ -152,12 +152,12 @@ public sealed override EnumInfo GetEnumInfo(RuntimeTypeHandle typeHandle) if (qTypeDefinition.IsNativeFormatMetadataBased) { - return NativeFormatEnumInfo.Create(typeHandle, qTypeDefinition.NativeFormatReader, qTypeDefinition.NativeFormatHandle); + return NativeFormatEnumInfo.Create(typeHandle, qTypeDefinition.NativeFormatReader, qTypeDefinition.NativeFormatHandle); } #if ECMA_METADATA_SUPPORT if (qTypeDefinition.IsEcmaFormatMetadataBased) { - return EcmaFormatEnumInfo.Create(typeHandle, qTypeDefinition.EcmaFormatReader, qTypeDefinition.EcmaFormatHandle); + return EcmaFormatEnumInfo.Create(typeHandle, qTypeDefinition.EcmaFormatReader, qTypeDefinition.EcmaFormatHandle); } #endif return null; diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/NativeFormatEnumInfo.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/NativeFormatEnumInfo.cs index 4ce2606b0ee4a..7e7d9a046bde2 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/NativeFormatEnumInfo.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/NativeFormatEnumInfo.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Numerics; using System.Reflection; using System.Reflection.Runtime.General; @@ -12,7 +13,8 @@ namespace Internal.Reflection.Execution { static class NativeFormatEnumInfo { - public static EnumInfo Create(RuntimeTypeHandle typeHandle, MetadataReader reader, TypeDefinitionHandle typeDefHandle) + public static EnumInfo Create(RuntimeTypeHandle typeHandle, MetadataReader reader, TypeDefinitionHandle typeDefHandle) + where TUnderlyingValue : struct, INumber { TypeDefinition typeDef = reader.GetTypeDefinition(typeDefHandle); @@ -29,7 +31,7 @@ public static EnumInfo Create(RuntimeTypeHandle typeHandle, MetadataReader reade } string[] names = new string[staticFieldCount]; - object[] values = new object[staticFieldCount]; + TUnderlyingValue[] values = new TUnderlyingValue[staticFieldCount]; int i = 0; foreach (FieldHandle fieldHandle in typeDef.Fields) @@ -38,7 +40,7 @@ public static EnumInfo Create(RuntimeTypeHandle typeHandle, MetadataReader reade if (0 != (field.Flags & FieldAttributes.Static)) { names[i] = field.Name.GetString(reader); - values[i] = field.DefaultValue.ParseConstantNumericValue(reader); + values[i] = (TUnderlyingValue)field.DefaultValue.ParseConstantNumericValue(reader); i++; } } @@ -50,7 +52,7 @@ public static EnumInfo Create(RuntimeTypeHandle typeHandle, MetadataReader reade isFlags = true; } - return new EnumInfo(RuntimeAugments.GetEnumUnderlyingType(typeHandle), values, names, isFlags); + return new EnumInfo(RuntimeAugments.GetEnumUnderlyingType(typeHandle), values, names, isFlags); } } } diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 6192bc819319e..4abe63d6d9cf3 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -1919,26 +1919,6 @@ struct TempEnumValue UINT64 value; }; -//******************************************************************************* -class TempEnumValueSorter : public CQuickSort -{ -public: - TempEnumValueSorter(TempEnumValue *pArray, SSIZE_T iCount) - : CQuickSort(pArray, iCount) { LIMITED_METHOD_CONTRACT; } - - int Compare(TempEnumValue *pFirst, TempEnumValue *pSecond) - { - LIMITED_METHOD_CONTRACT; - - if (pFirst->value == pSecond->value) - return 0; - if (pFirst->value > pSecond->value) - return 1; - else - return -1; - } -}; - extern "C" void QCALLTYPE Enum_GetValuesAndNames(QCall::TypeHandle pEnumType, QCall::ObjectHandleOnStack pReturnValues, QCall::ObjectHandleOnStack pReturnNames, BOOL fGetNames) { QCALL_CONTRACT; @@ -1946,120 +1926,51 @@ extern "C" void QCALLTYPE Enum_GetValuesAndNames(QCall::TypeHandle pEnumType, QC BEGIN_QCALL; TypeHandle th = pEnumType.AsTypeHandle(); - - if (!th.IsEnum()) - COMPlusThrow(kArgumentException, W("Arg_MustBeEnum")); + _ASSERTE(th.IsEnum()); MethodTable *pMT = th.AsMethodTable(); IMDInternalImport *pImport = pMT->GetMDImport(); StackSArray temps; - UINT64 previousValue = 0; HENUMInternalHolder fieldEnum(pImport); fieldEnum.EnumInit(mdtFieldDef, pMT->GetCl()); - // - // Note that we're fine treating signed types as unsigned, because all we really - // want to do is sort them based on a convenient strong ordering. - // - - BOOL sorted = TRUE; - - CorElementType type = pMT->GetInternalCorElementType(); + CorElementType type = pMT->GetClass()->GetInternalCorElementType(); mdFieldDef field; while (pImport->EnumNext(&fieldEnum, &field)) { DWORD dwFlags; IfFailThrow(pImport->GetFieldDefProps(field, &dwFlags)); - if (IsFdStatic(dwFlags)) - { - TempEnumValue temp; - - if (fGetNames) - IfFailThrow(pImport->GetNameOfFieldDef(field, &temp.name)); + if (!IsFdStatic(dwFlags)) + continue; - UINT64 value = 0; + TempEnumValue temp; - MDDefaultValue defaultValue; - IfFailThrow(pImport->GetDefaultValue(field, &defaultValue)); - - // The following code assumes that the address of all union members is the same. - static_assert_no_msg(offsetof(MDDefaultValue, m_byteValue) == offsetof(MDDefaultValue, m_usValue)); - static_assert_no_msg(offsetof(MDDefaultValue, m_ulValue) == offsetof(MDDefaultValue, m_ullValue)); - PVOID pValue = &defaultValue.m_byteValue; - - switch (type) { - case ELEMENT_TYPE_I1: - value = *((INT8 *)pValue); - break; - - case ELEMENT_TYPE_U1: - case ELEMENT_TYPE_BOOLEAN: - value = *((UINT8 *)pValue); - break; - - case ELEMENT_TYPE_I2: - value = *((INT16 *)pValue); - break; - - case ELEMENT_TYPE_U2: - case ELEMENT_TYPE_CHAR: - value = *((UINT16 *)pValue); - break; - - case ELEMENT_TYPE_I4: - IN_TARGET_32BIT(case ELEMENT_TYPE_I:) - value = *((INT32 *)pValue); - break; - - case ELEMENT_TYPE_U4: - IN_TARGET_32BIT(case ELEMENT_TYPE_U:) - value = *((UINT32 *)pValue); - break; - - case ELEMENT_TYPE_I8: - case ELEMENT_TYPE_U8: - IN_TARGET_64BIT(case ELEMENT_TYPE_I:) - IN_TARGET_64BIT(case ELEMENT_TYPE_U:) - value = *((INT64 *)pValue); - break; - - default: - break; - } - - temp.value = value; + if (fGetNames) + IfFailThrow(pImport->GetNameOfFieldDef(field, &temp.name)); - // - // Check to see if we are already sorted. This may seem extraneous, but is - // actually probably the normal case. - // + MDDefaultValue defaultValue = { }; + IfFailThrow(pImport->GetDefaultValue(field, &defaultValue)); - if (previousValue > value) - sorted = FALSE; - previousValue = value; + // The following code assumes that the address of all union members is the same. + static_assert_no_msg(offsetof(MDDefaultValue, m_byteValue) == offsetof(MDDefaultValue, m_usValue)); + static_assert_no_msg(offsetof(MDDefaultValue, m_ulValue) == offsetof(MDDefaultValue, m_ullValue)); + temp.value = defaultValue.m_ullValue; - temps.Append(temp); - } + temps.Append(temp); } TempEnumValue * pTemps = &(temps[0]); DWORD cFields = temps.GetCount(); - if (!sorted) - { - TempEnumValueSorter sorter(pTemps, cFields); - sorter.Sort(); - } - { GCX_COOP(); struct gc { - I8ARRAYREF values; + BASEARRAYREF values; PTRARRAYREF names; } gc; gc.values = NULL; @@ -2068,12 +1979,14 @@ extern "C" void QCALLTYPE Enum_GetValuesAndNames(QCall::TypeHandle pEnumType, QC GCPROTECT_BEGIN(gc); { - gc.values = (I8ARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_U8, cFields); + // The managed side expects ELEMENT_TYPE_U1 as the underlying type for boolean + gc.values = (BASEARRAYREF) AllocatePrimitiveArray((type == ELEMENT_TYPE_BOOLEAN) ? ELEMENT_TYPE_U1 : type, cFields); - INT64 *pToValues = gc.values->GetDirectPointerToNonObjectElements(); + BYTE* pToValues = gc.values->GetDataPtr(); + size_t elementSize = gc.values->GetComponentSize(); - for (DWORD i = 0; i < cFields; i++) { - pToValues[i] = pTemps[i].value; + for (DWORD i = 0; i < cFields; i++, pToValues += elementSize) { + memcpyNoGCRefs(pToValues, &pTemps[i].value, elementSize); } pReturnValues.Set(gc.values); diff --git a/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Operators.cs b/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Operators.cs index 6a16f18b34534..97f2815c7f379 100644 --- a/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Operators.cs +++ b/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/Semantics/Operators.cs @@ -1046,8 +1046,8 @@ private int WhichBofsIsBetter(BinOpFullSig bofs1, BinOpFullSig bofs2, CType type bt2 = WhichTypeIsBetter(bofs1.Type2(), bofs2.Type2(), type2); } - Debug.Assert(Enum.IsDefined(typeof(BetterType), bt1)); - Debug.Assert(Enum.IsDefined(typeof(BetterType), bt2)); + Debug.Assert(Enum.IsDefined(bt1)); + Debug.Assert(Enum.IsDefined(bt2)); int res = bt1 switch { BetterType.Left => -1, @@ -1550,7 +1550,7 @@ private int WhichUofsIsBetter(UnaOpFullSig uofs1, UnaOpFullSig uofs2, CType type bt = WhichTypeIsBetter(uofs1.GetType(), uofs2.GetType(), typeArg); } - Debug.Assert(Enum.IsDefined(typeof(BetterType), bt)); + Debug.Assert(Enum.IsDefined(bt)); return bt switch { BetterType.Left => -1, diff --git a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs index 023b22f198ee8..3045365bfc612 100644 --- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs +++ b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs @@ -1248,7 +1248,7 @@ public unsafe void SetValue(string? name, object value, RegistryValueKind valueK throw new ArgumentException(SR.Arg_RegValStrLenBug, nameof(name)); } - if (!Enum.IsDefined(typeof(RegistryValueKind), valueKind)) + if (!Enum.IsDefined(valueKind)) { throw new ArgumentException(SR.Arg_RegBadKeyKind, nameof(valueKind)); } diff --git a/src/libraries/System.ComponentModel.TypeConverter/tests/EnumConverterTest.cs b/src/libraries/System.ComponentModel.TypeConverter/tests/EnumConverterTest.cs index d36cd6d44a3bc..63d7b4300c141 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/tests/EnumConverterTest.cs +++ b/src/libraries/System.ComponentModel.TypeConverter/tests/EnumConverterTest.cs @@ -171,7 +171,7 @@ public static void ConvertTo_LongFlagsEnum_EnumArray() EnumConverter converter = new EnumConverter(typeof(LongFlagsEnum)); LongFlagsEnum value = LongFlagsEnum.Bit62 | LongFlagsEnum.Bit63; Enum[] result = (Enum[])converter.ConvertTo(null, null, value, typeof(Enum[])); - Assert.Equal(new Enum[] { LongFlagsEnum.Bit62, LongFlagsEnum.Bit63 }, result); + Assert.Equal(new Enum[] { LongFlagsEnum.Bit63, LongFlagsEnum.Bit62 }, result); } private static void VerifyArraysEqual(T[] expected, object actual) diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs index d1e5d6f939d81..8f2500db732c7 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs @@ -480,7 +480,7 @@ public ProcessPriorityClass PriorityClass } set { - if (!Enum.IsDefined(typeof(ProcessPriorityClass), value)) + if (!Enum.IsDefined(value)) { throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(ProcessPriorityClass)); } diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.cs index 761f1cd3b46d0..b2941f17dfd5a 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessStartInfo.cs @@ -161,7 +161,7 @@ public ProcessWindowStyle WindowStyle get => _windowStyle; set { - if (!Enum.IsDefined(typeof(ProcessWindowStyle), value)) + if (!Enum.IsDefined(value)) { throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(ProcessWindowStyle)); } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.cs index 55a880becb59e..441809b55b13c 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrinterSettings.cs @@ -371,7 +371,7 @@ public PrintRange PrintRange get { return _printRange; } set { - if (!Enum.IsDefined(typeof(PrintRange), value)) + if (!Enum.IsDefined(value)) throw new InvalidEnumArgumentException(nameof(value), unchecked((int)value), typeof(PrintRange)); _printRange = value; diff --git a/src/libraries/System.Private.CoreLib/src/System/Boolean.cs b/src/libraries/System.Private.CoreLib/src/System/Boolean.cs index d4e1015af66ea..cbfaebf66dfa7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Boolean.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Boolean.cs @@ -11,7 +11,6 @@ ** ===========================================================*/ -using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs index 67f4d5860f656..ff9a0e5f30ca2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs @@ -1,35 +1,35 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Numerics; + namespace System { public abstract partial class Enum { - internal sealed class EnumInfo + internal sealed class EnumInfo + where TUnderlyingValue : struct, INumber { public readonly bool HasFlagsAttribute; public readonly bool ValuesAreSequentialFromZero; - public readonly ulong[] Values; + public readonly TUnderlyingValue[] Values; public readonly string[] Names; // Each entry contains a list of sorted pair of enum field names and values, sorted by values - public EnumInfo(bool hasFlagsAttribute, ulong[] values, string[] names) + public EnumInfo(bool hasFlagsAttribute, TUnderlyingValue[] values, string[] names) { HasFlagsAttribute = hasFlagsAttribute; Values = values; Names = names; - // Store whether all of the values are sequential starting from zero. - ValuesAreSequentialFromZero = true; - for (int i = 0; i < values.Length; i++) - { - if (values[i] != (ulong)i) - { - ValuesAreSequentialFromZero = false; - break; - } - } + Array.Sort(keys: values, items: names); + + ValuesAreSequentialFromZero = AreSequentialFromZero(values); } + + /// Create a copy of . + public TUnderlyingValue[] CloneValues() => + new ReadOnlySpan(Values).ToArray(); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.cs index d35f3a46c46bf..4b148acfee72b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.cs @@ -1,332 +1,303 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers.Binary; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// The code below includes partial support for float/double and -// pointer sized 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/intptr/uintptr enums. There was a conscious decision -// made to not fix the whole stack to work well for them because of -// the right behavior is often unclear, and it is hard to test and -// very low value because of such enums cannot be expressed in C#. +// 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. + +#pragma warning disable 8500 // Allow taking address of managed types namespace System { + /// Provides the base class for enumerations. [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public abstract partial class Enum : ValueType, IComparable, IFormattable, IConvertible + [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + public abstract partial class Enum : ValueType, IComparable, ISpanFormattable, IConvertible { - #region Private Constants + /// Character used to separate flag enum values when formatted in a list. private const char EnumSeparatorChar = ','; - #endregion - - #region Private Static Methods - private string ValueToString() + /// Retrieves the name of the constant in the specified enumeration type that has the specified value. + /// The type of the enumeration. + /// The value of a particular enumerated constant in terms of its underlying type. + /// + /// A string containing the name of the enumerated constant in whose value is , + /// or if no such constant is found. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe string? GetName(TEnum value) where TEnum : struct, Enum { - ref byte data = ref this.GetRawData(); - return (InternalGetCorElementType()) switch - { - CorElementType.ELEMENT_TYPE_I1 => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_U1 => data.ToString(), - CorElementType.ELEMENT_TYPE_BOOLEAN => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_I2 => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_U2 => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_CHAR => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_I4 => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_U4 => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_R4 => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_I8 => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_U8 => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_R8 => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_I => Unsafe.As(ref data).ToString(), - CorElementType.ELEMENT_TYPE_U => Unsafe.As(ref data).ToString(), - _ => throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType), - }; + RuntimeType rt = (RuntimeType)typeof(TEnum); + Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); + + if (underlyingType == typeof(int)) return GetNameInlined(GetEnumInfo(rt), *(int*)&value); + if (underlyingType == typeof(uint)) return GetNameInlined(GetEnumInfo(rt), *(uint*)&value); + if (underlyingType == typeof(long)) return GetNameInlined(GetEnumInfo(rt), *(long*)&value); + if (underlyingType == typeof(ulong)) return GetNameInlined(GetEnumInfo(rt), *(ulong*)&value); + if (underlyingType == typeof(byte)) return GetNameInlined(GetEnumInfo(rt), *(byte*)&value); + if (underlyingType == typeof(sbyte)) return GetNameInlined(GetEnumInfo(rt), *(sbyte*)&value); + if (underlyingType == typeof(short)) return GetNameInlined(GetEnumInfo(rt), *(short*)&value); + if (underlyingType == typeof(ushort)) return GetNameInlined(GetEnumInfo(rt), *(ushort*)&value); + if (underlyingType == typeof(nint)) return GetNameInlined(GetEnumInfo(rt), *(nint*)&value); + if (underlyingType == typeof(nuint)) return GetNameInlined(GetEnumInfo(rt), *(nuint*)&value); + if (underlyingType == typeof(char)) return GetNameInlined(GetEnumInfo(rt), *(char*)&value); + if (underlyingType == typeof(float)) return GetNameInlined(GetEnumInfo(rt), *(float*)&value); + if (underlyingType == typeof(double)) return GetNameInlined(GetEnumInfo(rt), *(double*)&value); + if (underlyingType == typeof(bool)) return GetNameInlined(GetEnumInfo(rt), *(bool*)&value ? (byte)1 : (byte)0); + + throw CreateUnknownEnumTypeException(); } - private string ValueToHexString() + /// Retrieves the name of the constant in the specified enumeration type that has the specified value. + /// An enumeration type. + /// The value of a particular enumerated constant in terms of its underlying type. + /// + /// A string containing the name of the enumerated constant in whose value is , + /// or if no such constant is found. + /// + /// or is . + /// + /// is not an , or is neither of type + /// nor does it have the same underlying type as . + /// + public static string? GetName(Type enumType, object value) { - ref byte data = ref this.GetRawData(); - Span bytes = stackalloc byte[8]; - int length; - switch (InternalGetCorElementType()) - { - case CorElementType.ELEMENT_TYPE_I1: - case CorElementType.ELEMENT_TYPE_U1: - bytes[0] = data; - length = 1; - break; - case CorElementType.ELEMENT_TYPE_BOOLEAN: - return data != 0 ? "01" : "00"; - case CorElementType.ELEMENT_TYPE_I2: - case CorElementType.ELEMENT_TYPE_U2: - case CorElementType.ELEMENT_TYPE_CHAR: - BinaryPrimitives.WriteUInt16BigEndian(bytes, Unsafe.As(ref data)); - length = 2; - break; - case CorElementType.ELEMENT_TYPE_I4: - case CorElementType.ELEMENT_TYPE_U4: - BinaryPrimitives.WriteUInt32BigEndian(bytes, Unsafe.As(ref data)); - length = 4; - break; - case CorElementType.ELEMENT_TYPE_I8: - case CorElementType.ELEMENT_TYPE_U8: - BinaryPrimitives.WriteUInt64BigEndian(bytes, Unsafe.As(ref data)); - length = 8; - break; - default: - throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType); - } - - return HexConverter.ToString(bytes.Slice(0, length), HexConverter.Casing.Upper); + ArgumentNullException.ThrowIfNull(enumType); + return enumType.GetEnumName(value); } - private static string ValueToHexString(object value) + /// Retrieves the name of the constant in the specified enumeration type that has the specified value. + /// An enumeration type. + /// The value of a particular enumerated constant in terms of its underlying type, cast to a . + /// + /// A string containing the name of the enumerated constant in whose value is , + /// or if no such constant is found. + /// + internal static string? GetName(RuntimeType enumType, ulong uint64Value) { - return (Convert.GetTypeCode(value)) switch + // 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 to look it up. + Type underlyingType = enumType.GetEnumUnderlyingType(); + switch (Type.GetTypeCode(underlyingType)) // can't use InternalGetCorElementType as enumType may actually be the underlying type { - TypeCode.SByte => ((byte)(sbyte)value).ToString("X2", null), - TypeCode.Byte => ((byte)value).ToString("X2", null), - TypeCode.Boolean => ((bool)value) ? "01" : "00", - TypeCode.Int16 => ((ushort)(short)value).ToString("X4", null), - TypeCode.UInt16 => ((ushort)value).ToString("X4", null), - TypeCode.Char => ((ushort)(char)value).ToString("X4", null), - TypeCode.UInt32 => ((uint)value).ToString("X8", null), - TypeCode.Int32 => ((uint)(int)value).ToString("X8", null), - TypeCode.UInt64 => ((ulong)value).ToString("X16", null), - TypeCode.Int64 => ((ulong)(long)value).ToString("X16", null), - _ => throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType), - }; - } + case TypeCode.SByte: + if ((long)uint64Value < sbyte.MinValue || (long)uint64Value > sbyte.MaxValue) return null; + return GetName(GetEnumInfo(enumType), (sbyte)uint64Value); - internal static string? GetEnumName(RuntimeType enumType, ulong ulValue) - { - return GetEnumName(GetEnumInfo(enumType), ulValue); - } + case TypeCode.Byte: + if (uint64Value > byte.MaxValue) return null; + return GetName(GetEnumInfo(enumType), (byte)uint64Value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string? GetEnumName(EnumInfo enumInfo, ulong ulValue) - { - if (enumInfo.ValuesAreSequentialFromZero) - { - string[] names = enumInfo.Names; - if (ulValue < (ulong)names.Length) - { - return names[(uint)ulValue]; - } - } - else - { - int index = FindDefinedIndex(enumInfo.Values, ulValue); - if (index >= 0) - { - return enumInfo.Names[index]; - } - } + case TypeCode.Boolean: + if (uint64Value > 1) return null; + return GetName(GetEnumInfo(enumType), (byte)uint64Value); - return null; // return null so the caller knows to .ToString() the input - } + case TypeCode.Int16: + if ((long)uint64Value < short.MinValue || (long)uint64Value > short.MaxValue) return null; + return GetName(GetEnumInfo(enumType), (short)uint64Value); - private static string? InternalFormat(RuntimeType enumType, ulong value) - { - EnumInfo enumInfo = GetEnumInfo(enumType); + case TypeCode.UInt16: + if (uint64Value > ushort.MaxValue) return null; + return GetName(GetEnumInfo(enumType), (ushort)uint64Value); - if (!enumInfo.HasFlagsAttribute) + case TypeCode.Char: + if (uint64Value > char.MaxValue) return null; + return GetName(GetEnumInfo(enumType), (char)uint64Value); + + case TypeCode.UInt32: + if (uint64Value > uint.MaxValue) return null; + return GetName(GetEnumInfo(enumType), (uint)uint64Value); + + case TypeCode.Int32: + if ((long)uint64Value < int.MinValue || (long)uint64Value > int.MaxValue) return null; + return GetName(GetEnumInfo(enumType), (int)uint64Value); + + case TypeCode.UInt64: + return GetName(GetEnumInfo(enumType), uint64Value); + + case TypeCode.Int64: + return GetName(GetEnumInfo(enumType), (long)uint64Value); + }; + + if (underlyingType == typeof(nint)) { - return GetEnumName(enumInfo, value); + if ((long)uint64Value < nint.MinValue || (long)uint64Value > nint.MaxValue) return null; + return GetName(GetEnumInfo(enumType), (nint)uint64Value); } - else // These are flags OR'ed together (We treat everything as unsigned types) + + if (underlyingType == typeof(nuint)) { - return InternalFlagsFormat(enumInfo, value); + if (uint64Value > nuint.MaxValue) return null; + return GetName(GetEnumInfo(enumType), (nuint)uint64Value); } - } - private static string? InternalFlagsFormat(RuntimeType enumType, ulong result) - { - return InternalFlagsFormat(GetEnumInfo(enumType), result); + throw CreateUnknownEnumTypeException(); } - private static string? InternalFlagsFormat(EnumInfo enumInfo, ulong resultValue) + [MethodImpl(MethodImplOptions.NoInlining)] + private static string? GetName(EnumInfo enumInfo, TUnderlyingValue value) + where TUnderlyingValue : struct, INumber => + GetNameInlined(enumInfo, value); + + /// Look up the name for the specified underlying value using the cached for the associated enum. + /// The type of the value underlying the associated enum. + /// The cached for the enum type. + /// The underlying value for which we're searching. + /// The name of the value if found; otherwise, . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string? GetNameInlined(EnumInfo enumInfo, TUnderlyingValue value) + where TUnderlyingValue : struct, INumber { string[] names = enumInfo.Names; - ulong[] values = enumInfo.Values; - Debug.Assert(names.Length == values.Length); - - // 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 == 0) - { - return values.Length > 0 && values[0] == 0 ? - names[0] : - "0"; - } - // 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 foundItems = stackalloc int[64]; - - // 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 index = values.Length - 1; - while (index >= 0) + // 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 (values[index] == resultValue) + if (Unsafe.SizeOf() <= sizeof(int)) { - return names[index]; + // 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]; + } } - - if (values[index] < resultValue) + else { - break; + // Handle the remaining types. + if (ulong.CreateTruncating(value) < (ulong)names.Length) + { + return names[uint.CreateTruncating(value)]; + } } - - index--; } - - // Now look for multiple matches, storing the indices of the values - // into our span. - int resultLength = 0, foundItemsCount = 0; - while (index >= 0) + else { - ulong currentValue = values[index]; - if (index == 0 && currentValue == 0) - { - break; - } - - if ((resultValue & currentValue) == currentValue) + // 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) { - resultValue -= currentValue; - foundItems[foundItemsCount++] = index; - resultLength = checked(resultLength + names[index].Length); + return names[index]; } - - 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 (resultValue != 0) - { - return null; } - // We know what strings to concatenate. Do so. - - Debug.Assert(foundItemsCount > 0); - const int SeparatorStringLength = 2; // ", " - string result = string.FastAllocateString(checked(resultLength + (SeparatorStringLength * (foundItemsCount - 1)))); - - Span resultSpan = new Span(ref result.GetRawStringData(), result.Length); - string name = names[foundItems[--foundItemsCount]]; - name.CopyTo(resultSpan); - resultSpan = resultSpan.Slice(name.Length); - while (--foundItemsCount >= 0) - { - resultSpan[0] = EnumSeparatorChar; - resultSpan[1] = ' '; - resultSpan = resultSpan.Slice(2); - - name = names[foundItems[foundItemsCount]]; - name.CopyTo(resultSpan); - resultSpan = resultSpan.Slice(name.Length); - } - Debug.Assert(resultSpan.IsEmpty); - - return result; + // Return null so the caller knows no individual named value could be found. + return null; } - internal static ulong ToUInt64(object value) + /// Retrieves an array of the names of the constants in a specified enumeration type. + /// The type of the enumeration. + /// A string array of the names of the constants in . + public static string[] GetNames() where TEnum : struct, Enum { - // Helper function to silently convert the value to UInt64 from the other base types for enum without throwing an exception. - // This is need since the Convert functions do overflow checks. - TypeCode typeCode = Convert.GetTypeCode(value); - ulong result = typeCode switch - { - TypeCode.SByte => (ulong)(sbyte)value, - TypeCode.Byte => (byte)value, - TypeCode.Boolean => (bool)value ? 1UL : 0UL, - TypeCode.Int16 => (ulong)(short)value, - TypeCode.UInt16 => (ushort)value, - TypeCode.Char => (char)value, - TypeCode.UInt32 => (uint)value, - TypeCode.Int32 => (ulong)(int)value, - TypeCode.UInt64 => (ulong)value, - TypeCode.Int64 => (ulong)(long)value, - _ => throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType), - }; - return result; - } - - private static ulong ToUInt64(TEnum value) where TEnum : struct, Enum => - Type.GetTypeCode(typeof(TEnum)) switch - { - TypeCode.SByte => (ulong)Unsafe.As(ref value), - TypeCode.Byte => Unsafe.As(ref value), - TypeCode.Boolean => Unsafe.As(ref value) ? 1UL : 0UL, - TypeCode.Int16 => (ulong)Unsafe.As(ref value), - TypeCode.UInt16 => Unsafe.As(ref value), - TypeCode.Char => Unsafe.As(ref value), - TypeCode.UInt32 => Unsafe.As(ref value), - TypeCode.Int32 => (ulong)Unsafe.As(ref value), - TypeCode.UInt64 => Unsafe.As(ref value), - TypeCode.Int64 => (ulong)Unsafe.As(ref value), - _ => throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType), - }; - #endregion + string[] names; - #region Public Static Methods - public static string? GetName(TEnum value) where TEnum : struct, Enum => - GetEnumName((RuntimeType)typeof(TEnum), ToUInt64(value)); - - public static string? GetName(Type enumType, object value) - { - ArgumentNullException.ThrowIfNull(enumType); - return enumType.GetEnumName(value); + RuntimeType rt = (RuntimeType)typeof(TEnum); + Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); + + // Get the cached names array. + if (underlyingType == typeof(int)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(uint)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(long)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(ulong)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(byte)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(sbyte)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(short)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(ushort)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(nint)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(nuint)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(char)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(float)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(double)) names = GetEnumInfo(rt).Names; + else if (underlyingType == typeof(bool)) names = GetEnumInfo(rt).Names; + else throw CreateUnknownEnumTypeException(); + + // Return a clone of the array to avoid exposing the cached array instance. + return new ReadOnlySpan(names).ToArray(); } - public static string[] GetNames() where TEnum : struct, Enum => - new ReadOnlySpan(InternalGetNames((RuntimeType)typeof(TEnum))).ToArray(); - + /// Retrieves an array of the names of the constants in a specified enumeration. + /// An enumeration type. + /// A string array of the names of the constants in . + /// is null. + /// is not an . public static string[] GetNames(Type enumType) { ArgumentNullException.ThrowIfNull(enumType); return enumType.GetEnumNames(); } - internal static string[] InternalGetNames(RuntimeType enumType) => - // Get all of the names - GetEnumInfo(enumType).Names; + /// Gets the cached names array for the specified enum type, without making a copy. + /// The returned array should not be exposed outside of this assembly. + internal static string[] GetNamesNoCopy(RuntimeType enumType) + { + Debug.Assert(enumType.IsActualEnum); + + return InternalGetCorElementType(enumType) switch + { + CorElementType.ELEMENT_TYPE_I1 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_U1 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_I2 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_U2 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_I4 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_U4 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_I8 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_U8 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_R4 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_R8 => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_I => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_U => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_CHAR => GetEnumInfo(enumType).Names, + CorElementType.ELEMENT_TYPE_BOOLEAN => GetEnumInfo(enumType).Names, + _ => throw CreateUnknownEnumTypeException(), + }; + } + /// Returns the underlying type of the specified enumeration. + /// The enumeration whose underlying type will be retrieved. + /// The underlying type of . + /// is null. + /// is not an . public static Type GetUnderlyingType(Type enumType) { ArgumentNullException.ThrowIfNull(enumType); return enumType.GetEnumUnderlyingType(); } -#if !NATIVEAOT - public static TEnum[] GetValues() where TEnum : struct, Enum => - (TEnum[])GetValues(typeof(TEnum)); -#endif + /// Retrieves an array of the values of the constants in a specified enumeration type. + /// The type of the enumeration. + /// An array that contains the values of the constants in . + public static TEnum[] GetValues() where TEnum : struct, Enum + { + Array values = GetValuesAsUnderlyingTypeNoCopy((RuntimeType)typeof(TEnum)); + var result = new TEnum[values.Length]; + Array.Copy(values, result, values.Length); + return result; + } + /// Retrieves an array of the values of the constants in a specified enumeration. + /// An enumeration type. + /// An array that contains the values of the constants in . + /// is null. + /// is not an . + /// + /// The method is invoked by reflection in a reflection-only context, or is a type from an assembly loaded in a reflection-only context. + /// [RequiresDynamicCode("It might not be possible to create an array of the enum type at runtime. Use the GetValues overload or the GetValuesAsUnderlyingType method instead.")] public static Array GetValues(Type enumType) { @@ -334,11 +305,9 @@ public static Array GetValues(Type enumType) return enumType.GetEnumValues(); } - /// - /// Retrieves an array of the values of the underlying type constants in a specified enumeration type. - /// + /// Retrieves an array of the values of the underlying type constants in a specified enumeration type. /// An enumeration type. - /// /// + /// /// 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 enumeration or on a platform where run-time code generation is not available. /// @@ -346,25 +315,76 @@ public static Array GetValues(Type enumType) public static Array GetValuesAsUnderlyingType() where TEnum : struct, Enum => typeof(TEnum).GetEnumValuesAsUnderlyingType(); - /// - /// Retrieves an array of the values of the underlying type constants in a specified enumeration. - /// + /// Retrieves an array of the values of the underlying type constants in a specified enumeration. /// An enumeration type. /// /// 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 enumeration or on a platform where run-time code generation is not available. + /// For example, you might use this method for the MetadataLoadContext enumeration or on a platform where run-time code generation is not available. /// /// An array that contains the values of the underlying type constants in . - /// - /// is null. - /// - /// is not an enumeration type. + /// is null. + /// is not an enumeration type. public static Array GetValuesAsUnderlyingType(Type enumType) { ArgumentNullException.ThrowIfNull(enumType); return enumType.GetEnumValuesAsUnderlyingType(); } + /// Retrieves an array of the values of the underlying type constants in a specified enumeration type. + internal static Array GetValuesAsUnderlyingType(RuntimeType enumType) + { + Debug.Assert(enumType.IsActualEnum); + + return InternalGetCorElementType(enumType) switch + { + CorElementType.ELEMENT_TYPE_I1 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_U1 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_I2 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_U2 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_I4 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_U4 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_I8 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_U8 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_R4 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_R8 => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_I => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_U => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_CHAR => GetEnumInfo(enumType, getNames: false).CloneValues(), + CorElementType.ELEMENT_TYPE_BOOLEAN => CopyByteArrayToNewBoolArray(GetEnumInfo(enumType, getNames: false).Values), + _ => throw CreateUnknownEnumTypeException(), + }; + } + + /// Retrieves the cached array of the values of the underlying type constants in a specified enumeration. + /// The returned array should not be exposed outside of this assembly. + internal static Array GetValuesAsUnderlyingTypeNoCopy(RuntimeType enumType) + { + Debug.Assert(enumType.IsActualEnum); + + return InternalGetCorElementType(enumType) switch + { + CorElementType.ELEMENT_TYPE_I1 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_U1 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_I2 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_U2 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_I4 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_U4 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_I8 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_U8 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_R4 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_R8 => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_I => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_U => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_CHAR => GetEnumInfo(enumType, getNames: false).Values, + CorElementType.ELEMENT_TYPE_BOOLEAN => CopyByteArrayToNewBoolArray(GetEnumInfo(enumType, getNames: false).Values), // this is the only case that clones, out of necessity + _ => throw CreateUnknownEnumTypeException(), + }; + } + + /// Determines whether one or more bit fields are set in the current instance. + /// An enumeration value. + /// if the bit field or bit fields that are set in flag are also set in the current instance; otherwise, . + /// is a different type than the current instance. [Intrinsic] public bool HasFlag(Enum flag) { @@ -385,6 +405,7 @@ public bool HasFlag(Enum flag) byte flagsValue = pFlagsValue; return (pThisValue & flagsValue) == flagsValue; } + case CorElementType.ELEMENT_TYPE_I2: case CorElementType.ELEMENT_TYPE_U2: case CorElementType.ELEMENT_TYPE_CHAR: @@ -392,6 +413,7 @@ public bool HasFlag(Enum flag) ushort flagsValue = Unsafe.As(ref pFlagsValue); return (Unsafe.As(ref pThisValue) & flagsValue) == flagsValue; } + case CorElementType.ELEMENT_TYPE_I4: case CorElementType.ELEMENT_TYPE_U4: #if TARGET_32BIT @@ -403,6 +425,7 @@ public bool HasFlag(Enum flag) uint flagsValue = Unsafe.As(ref pFlagsValue); return (Unsafe.As(ref pThisValue) & flagsValue) == flagsValue; } + case CorElementType.ELEMENT_TYPE_I8: case CorElementType.ELEMENT_TYPE_U8: #if TARGET_64BIT @@ -414,65 +437,122 @@ public bool HasFlag(Enum flag) ulong flagsValue = Unsafe.As(ref pFlagsValue); return (Unsafe.As(ref pThisValue) & flagsValue) == flagsValue; } + default: Debug.Fail("Unknown enum underlying type"); return false; } } - internal static ulong[] InternalGetValues(RuntimeType enumType) + /// Marshals a byte[] to a bool[]. + /// The input byte array. + /// A new bool[] containing true whereever a byte was non-zero and false wherever a byte was zero. + /// + /// Since bool isn't a numeric type, we use an EnumInfo{byte} for bool-based enums. This method is used to translate + /// that EnumInfo's Values back to bools. + /// + private static bool[] CopyByteArrayToNewBoolArray(byte[] bytes) { - // Get all of the values - return GetEnumInfo(enumType, getNames: false).Values; + bool[] result = new bool[bytes.Length]; + for (int i = 0; i < bytes.Length; i++) + { + result[i] = bytes[i] != 0; + } + return result; } - public static bool IsDefined(TEnum value) where TEnum : struct, Enum + /// Returns a telling whether a given integral value, or its name as a string, exists in a specified enumeration. + /// The type of the enumeration. + /// The value or name of a constant in . + /// if a given integral value exists in a specified enumeration; , otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool IsDefined(TEnum value) where TEnum : struct, Enum { - RuntimeType enumType = (RuntimeType)typeof(TEnum); - EnumInfo info = GetEnumInfo(enumType, getNames: false); - ulong ulValue = ToUInt64(value); - ulong[] ulValues = info.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. Otherwise, search for the value. - return - info.ValuesAreSequentialFromZero ? ulValue < (ulong)ulValues.Length : - FindDefinedIndex(ulValues, ulValue) >= 0; + RuntimeType rt = (RuntimeType)typeof(TEnum); + Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); + + if (underlyingType == typeof(int)) return IsDefinedPrimitive(rt, *(int*)&value); + if (underlyingType == typeof(uint)) return IsDefinedPrimitive(rt, *(uint*)&value); + if (underlyingType == typeof(long)) return IsDefinedPrimitive(rt, *(long*)&value); + if (underlyingType == typeof(ulong)) return IsDefinedPrimitive(rt, *(ulong*)&value); + if (underlyingType == typeof(byte)) return IsDefinedPrimitive(rt, *(byte*)&value); + if (underlyingType == typeof(sbyte)) return IsDefinedPrimitive(rt, *(sbyte*)&value); + if (underlyingType == typeof(short)) return IsDefinedPrimitive(rt, *(short*)&value); + if (underlyingType == typeof(ushort)) return IsDefinedPrimitive(rt, *(ushort*)&value); + if (underlyingType == typeof(nint)) return IsDefinedPrimitive(rt, *(nint*)&value); + if (underlyingType == typeof(nuint)) return IsDefinedPrimitive(rt, *(nuint*)&value); + if (underlyingType == typeof(char)) return IsDefinedPrimitive(rt, *(char*)&value); + if (underlyingType == typeof(float)) return IsDefinedPrimitive(rt, *(float*)&value); + if (underlyingType == typeof(double)) return IsDefinedPrimitive(rt, *(double*)&value); + if (underlyingType == typeof(bool)) return IsDefinedPrimitive(rt, *(bool*)&value ? (byte)1 : (byte)0); + + throw CreateUnknownEnumTypeException(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FindDefinedIndex(ulong[] ulValues, ulong ulValue) + /// Gets whether the specified individual value is defined in the specified enum. + internal static bool IsDefinedPrimitive(RuntimeType enumType, TUnderlyingValue value) + where TUnderlyingValue : struct, INumber { - // Binary searching has a higher constant overhead than linear. - // For smaller enums, use IndexOf. For larger enums, use BinarySearch. - // This threshold can be tweaked over time as optimizations evolve. - const int NumberOfValuesThreshold = 32; - - int ulValuesLength = ulValues.Length; - ref ulong start = ref MemoryMarshal.GetArrayDataReference(ulValues); - return ulValuesLength <= NumberOfValuesThreshold ? - SpanHelpers.IndexOfValueType(ref Unsafe.As(ref start), (long)ulValue, ulValuesLength) : - SpanHelpers.BinarySearch(ref start, ulValuesLength, ulValue); + EnumInfo enumInfo = GetEnumInfo(enumType, getNames: false); + TUnderlyingValue[] 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; } + /// Returns a telling whether a given integral value, or its name as a string, exists in a specified enumeration. + /// An enumeration type. + /// The value or name of a constant in . + /// if a constant in has a value equal to value; otherwise, . + /// or is . + /// + /// is not an , + /// or the type of is an enumeration but it is not an enumeration of type , + /// or the type of is not an underlying type of . + /// public static bool IsDefined(Type enumType, object value) { ArgumentNullException.ThrowIfNull(enumType); - return enumType.IsEnumDefined(value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FindDefinedIndex(TUnderlyingValue[] values, TUnderlyingValue value) + where TUnderlyingValue : struct, IEquatable, IComparable + { + // 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 span = values; + return values.Length <= NumberOfValuesThreshold ? + span.IndexOf(value) : + SpanHelpers.BinarySearch(span, value); + } + + /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. + /// An enumeration type. + /// A string containing the name or value to convert. + /// An of type whose value is represented by . + /// is . + /// is not an . + /// is either an empty string or only contains white space. + /// is a name, but not one of the named constants defined for the enumeration. + /// is outside the range of the underlying type of public static object Parse(Type enumType, string value) => Parse(enumType, value, ignoreCase: false); - /// - /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. - /// + /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. /// An enumeration type. /// A span containing the name or value to convert. - /// - /// An object of type whose value is represented by . - /// + /// An of type whose value is represented by . /// is . /// is not an . /// is either an empty string or only contains white space. @@ -481,22 +561,39 @@ public static object Parse(Type enumType, string value) => public static object Parse(Type enumType, ReadOnlySpan value) => Parse(enumType, value, ignoreCase: false); + /// + /// 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. + /// + /// An enumeration type. + /// A string containing the name or value to convert. + /// to ignore case; to regard case. + /// An of type whose value is represented by . + /// is . + /// is not an . + /// is either an empty string or only contains white space. + /// is a name, but not one of the named constants defined for the enumeration. + /// is outside the range of the underlying type of public static object Parse(Type enumType, string value, bool ignoreCase) { - bool success = TryParse(enumType, value, ignoreCase, throwOnFailure: true, out object? result); - Debug.Assert(success); - return result!; + 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; } /// - /// 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. + /// 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. /// /// An enumeration type. /// A span containing the name or value to convert. /// to ignore case; to regard case. - /// - /// An object of type whose value is represented by . - /// + /// An of type whose value is represented by . /// is . /// is not an . /// is either an empty string or only contains white space. @@ -505,16 +602,21 @@ public static object Parse(Type enumType, string value, bool ignoreCase) public static object Parse(Type enumType, ReadOnlySpan value, bool ignoreCase) { bool success = TryParse(enumType, value, ignoreCase, throwOnFailure: true, out object? result); - Debug.Assert(success); - return result!; + Debug.Assert(success && result is not null); + return result; } + /// Converts the string representation of the name or numeric value of one or more enumerated constants specified by to an equivalent enumerated object. + /// An enumeration type. + /// A string containing the name or value to convert. + /// An object of type whose value is represented by . + /// is . + /// is not an type + /// does not contain enumeration information public static TEnum Parse(string value) where TEnum : struct => Parse(value, ignoreCase: false); - /// - /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants specified by to an equivalent enumerated object. - /// + /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants specified by to an equivalent enumerated object. /// An enumeration type. /// A span containing the name or value to convert. /// An object of type whose value is represented by . @@ -523,15 +625,32 @@ public static TEnum Parse(string value) where TEnum : struct => public static TEnum Parse(ReadOnlySpan value) where TEnum : struct => Parse(value, ignoreCase: false); + /// + /// Converts the string representation of the name or numeric value of one or more enumerated constants specified by to an equivalent enumerated object. + /// A parameter specifies whether the operation is case-insensitive. + /// + /// An enumeration type. + /// A string containing the name or value to convert. + /// to ignore case; to regard case. + /// An object of type whose value is represented by . + /// is . + /// is not an type + /// does not contain enumeration information public static TEnum Parse(string value, bool ignoreCase) where TEnum : struct { - bool success = TryParse(value, ignoreCase, throwOnFailure: true, out TEnum result); + if (value is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value); + } + + bool success = TryParse(value.AsSpan(), ignoreCase, throwOnFailure: true, out TEnum result); Debug.Assert(success); return result; } /// - /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants specified by to an equivalent enumerated object. A parameter specifies whether the operation is case-insensitive. + /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants specified by to an equivalent enumerated object. + /// A parameter specifies whether the operation is case-insensitive. /// /// An enumeration type. /// A span containing the name or value to convert. @@ -541,17 +660,20 @@ public static TEnum Parse(string value, bool ignoreCase) where TEnum : st /// does not contain enumeration information public static TEnum Parse(ReadOnlySpan value, bool ignoreCase) where TEnum : struct { - bool success = TryParse(value, ignoreCase, throwOnFailure: true, out TEnum result); + bool success = TryParse(value, ignoreCase, throwOnFailure: true, out TEnum result); Debug.Assert(success); return result; } + /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. + /// The enum type to use for parsing. + /// The string representation of the name or numeric value of one or more enumerated constants. + /// When this method returns , an object containing an enumeration constant representing the parsed value. + /// if the conversion succeeded; otherwise. public static bool TryParse(Type enumType, string? value, [NotNullWhen(true)] out object? result) => TryParse(enumType, value, ignoreCase: false, out result); - /// - /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. - /// + /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. /// The enum type to use for parsing. /// The span representation of the name or numeric value of one or more enumerated constants. /// When this method returns , an object containing an enumeration constant representing the parsed value. @@ -559,429 +681,386 @@ public static bool TryParse(Type enumType, string? value, [NotNullWhen(true)] ou public static bool TryParse(Type enumType, ReadOnlySpan value, [NotNullWhen(true)] out object? result) => TryParse(enumType, value, ignoreCase: false, out result); - public static bool TryParse(Type enumType, string? value, bool ignoreCase, [NotNullWhen(true)] out object? result) => - TryParse(enumType, value, ignoreCase, throwOnFailure: false, out result); - /// - /// 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. + /// 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. /// /// The enum type to use for parsing. - /// The span representation of the name or numeric value of one or more enumerated constants. + /// The string representation of the name or numeric value of one or more enumerated constants. /// to read in case insensitive mode; to read in case sensitive mode. /// When this method returns , an object containing an enumeration constant representing the parsed value. /// if the conversion succeeded; otherwise. - public static bool TryParse(Type enumType, ReadOnlySpan value, bool ignoreCase, [NotNullWhen(true)] out object? result) => - TryParse(enumType, value, ignoreCase, throwOnFailure: false, out result); - - private static bool TryParse(Type enumType, string? value, bool ignoreCase, bool throwOnFailure, [NotNullWhen(true)] out object? result) + public static bool TryParse(Type enumType, string? value, bool ignoreCase, [NotNullWhen(true)] out object? result) { - if (value == null) + if (value is not null) { - if (throwOnFailure) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value); - } - result = null; - return false; + return TryParse(enumType, value.AsSpan(), ignoreCase, throwOnFailure: false, out result); } - return TryParse(enumType, value.AsSpan(), ignoreCase, throwOnFailure, out result); + result = null; + return false; } - private static bool TryParse(Type enumType, ReadOnlySpan value, bool ignoreCase, bool throwOnFailure, [NotNullWhen(true)] out object? result) + /// + /// 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. + /// + /// The enum type to use for parsing. + /// The span representation of the name or numeric value of one or more enumerated constants. + /// to read in case insensitive mode; to read in case sensitive mode. + /// When this method returns , an object containing an enumeration constant representing the parsed value. + /// if the conversion succeeded; otherwise. + public static bool TryParse(Type enumType, ReadOnlySpan value, bool ignoreCase, [NotNullWhen(true)] out object? result) => + TryParse(enumType, value, ignoreCase, throwOnFailure: false, out result); + + /// Core implementation for all non-generic {Try}Parse methods. + private static unsafe bool TryParse(Type enumType, ReadOnlySpan 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); - value = value.TrimStart(); - if (value.Length == 0) + + switch (InternalGetCorElementType(rt)) { - if (throwOnFailure) - { - throw new ArgumentException(SR.Arg_MustContainEnumInfo, nameof(value)); - } - result = null; - return false; + case CorElementType.ELEMENT_TYPE_I1: + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out *(sbyte*)&longScratch); + longScratch = *(sbyte*)&longScratch; + break; + + case CorElementType.ELEMENT_TYPE_U1: + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out *(byte*)&longScratch); + longScratch = *(byte*)&longScratch; + break; + + case CorElementType.ELEMENT_TYPE_I2: + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out *(short*)&longScratch); + longScratch = *(short*)&longScratch; + break; + + case CorElementType.ELEMENT_TYPE_U2: + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out *(ushort*)&longScratch); + longScratch = *(ushort*)&longScratch; + break; + + case CorElementType.ELEMENT_TYPE_I4: + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out *(int*)&longScratch); + longScratch = *(int*)&longScratch; + break; + + case CorElementType.ELEMENT_TYPE_U4: + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out *(uint*)&longScratch); + longScratch = *(uint*)&longScratch; + break; + + case CorElementType.ELEMENT_TYPE_I8: + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out longScratch); + break; + + case CorElementType.ELEMENT_TYPE_U8: + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out *(ulong*)&longScratch); + break; + + default: + parsed = TryParseRareTypes(rt, value, ignoreCase, throwOnFailure, out longScratch); + break; } - int intResult; - uint uintResult; - bool parsed; + result = parsed ? InternalBoxEnum(rt, longScratch) : null; + return parsed; - switch (Type.GetTypeCode(rt)) + [MethodImpl(MethodImplOptions.NoInlining)] + static bool TryParseRareTypes(RuntimeType rt, ReadOnlySpan value, bool ignoreCase, bool throwOnFailure, [NotNullWhen(true)] out long result) { - case TypeCode.SByte: - parsed = TryParseInt32Enum(rt, value, sbyte.MinValue, sbyte.MaxValue, ignoreCase, throwOnFailure, TypeCode.SByte, out intResult); - result = parsed ? InternalBoxEnum(rt, intResult) : null; - return parsed; + bool parsed; - case TypeCode.Int16: - parsed = TryParseInt32Enum(rt, value, short.MinValue, short.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int16, out intResult); - result = parsed ? InternalBoxEnum(rt, intResult) : null; - return parsed; + switch (InternalGetCorElementType(rt)) + { + case CorElementType.ELEMENT_TYPE_R4: + { + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out float localResult); + result = BitConverter.SingleToInt32Bits(localResult); + } + break; - case TypeCode.Int32: - parsed = TryParseInt32Enum(rt, value, int.MinValue, int.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int32, out intResult); - result = parsed ? InternalBoxEnum(rt, intResult) : null; - return parsed; + case CorElementType.ELEMENT_TYPE_R8: + { + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out double localResult); + result = BitConverter.DoubleToInt64Bits(localResult); + } + break; - case TypeCode.Byte: - parsed = TryParseUInt32Enum(rt, value, byte.MaxValue, ignoreCase, throwOnFailure, TypeCode.Byte, out uintResult); - result = parsed ? InternalBoxEnum(rt, uintResult) : null; - return parsed; + case CorElementType.ELEMENT_TYPE_I: + { + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out nint localResult); + result = localResult; + } + break; - case TypeCode.UInt16: - parsed = TryParseUInt32Enum(rt, value, ushort.MaxValue, ignoreCase, throwOnFailure, TypeCode.UInt16, out uintResult); - result = parsed ? InternalBoxEnum(rt, uintResult) : null; - return parsed; + case CorElementType.ELEMENT_TYPE_U: + { + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out nuint localResult); + result = (long)localResult; + } + break; - case TypeCode.UInt32: - parsed = TryParseUInt32Enum(rt, value, uint.MaxValue, ignoreCase, throwOnFailure, TypeCode.UInt32, out uintResult); - result = parsed ? InternalBoxEnum(rt, uintResult) : null; - return parsed; + case CorElementType.ELEMENT_TYPE_CHAR: + { + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out char localResult); + result = localResult; + } + break; - case TypeCode.Int64: - parsed = TryParseInt64Enum(rt, value, ignoreCase, throwOnFailure, out long longResult); - result = parsed ? InternalBoxEnum(rt, longResult) : null; - return parsed; + case CorElementType.ELEMENT_TYPE_BOOLEAN: + { + parsed = TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out byte localResult); + result = localResult; + } + break; - case TypeCode.UInt64: - parsed = TryParseUInt64Enum(rt, value, ignoreCase, throwOnFailure, out ulong ulongResult); - result = parsed ? InternalBoxEnum(rt, (long)ulongResult) : null; - return parsed; + default: + throw CreateUnknownEnumTypeException(); + } - default: - return TryParseRareEnum(rt, value, ignoreCase, throwOnFailure, out result); + return parsed; } } + /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. + /// + /// The string representation of the name or numeric value of one or more enumerated constants. + /// When this method returns , an object containing an enumeration constant representing the parsed value. + /// if the conversion succeeded; otherwise. + /// is not an enumeration type public static bool TryParse([NotNullWhen(true)] string? value, out TEnum result) where TEnum : struct => - TryParse(value, ignoreCase: false, out result); + TryParse(value, ignoreCase: false, out result); - /// - /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. - /// + /// Converts the span of chars representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. /// - /// The span representation of the name or numeric value of one or more enumerated constants. + /// The span of chars representation of the name or numeric value of one or more enumerated constants. /// When this method returns , an object containing an enumeration constant representing the parsed value. /// if the conversion succeeded; otherwise. /// is not an enumeration type public static bool TryParse(ReadOnlySpan value, out TEnum result) where TEnum : struct => - TryParse(value, ignoreCase: false, out result); - - public static bool TryParse([NotNullWhen(true)] string? value, bool ignoreCase, out TEnum result) where TEnum : struct => - TryParse(value, ignoreCase, throwOnFailure: false, out result); + TryParse(value, ignoreCase: false, throwOnFailure: false, out result); /// - /// 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. The return value indicates whether the conversion succeeded. + /// 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. /// /// - /// The span representation of the name or numeric value of one or more enumerated constants. + /// The string representation of the name or numeric value of one or more enumerated constants. /// to ignore case; to consider case. /// When this method returns , an object containing an enumeration constant representing the parsed value. /// if the conversion succeeded; otherwise. /// is not an enumeration type - public static bool TryParse(ReadOnlySpan value, bool ignoreCase, out TEnum result) where TEnum : struct => - TryParse(value, ignoreCase, throwOnFailure: false, out result); - - private static bool TryParse(string? value, bool ignoreCase, bool throwOnFailure, out TEnum result) where TEnum : struct + public static bool TryParse([NotNullWhen(true)] string? value, bool ignoreCase, out TEnum result) where TEnum : struct { - if (value == null) + if (value is not null) { - if (throwOnFailure) - { - ArgumentNullException.Throw(nameof(value)); - } - result = default; - return false; + return TryParse(value.AsSpan(), ignoreCase, throwOnFailure: false, out result); } - return TryParse(value.AsSpan(), ignoreCase, throwOnFailure, out result); + result = default; + return false; } + /// + /// 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. + /// + /// + /// The span representation of the name or numeric value of one or more enumerated constants. + /// to ignore case; to consider case. + /// When this method returns , an object containing an enumeration constant representing the parsed value. + /// if the conversion succeeded; otherwise. + /// is not an enumeration type + public static bool TryParse(ReadOnlySpan value, bool ignoreCase, out TEnum result) where TEnum : struct => + TryParse(value, ignoreCase, throwOnFailure: false, out result); + + /// Core implementation for all generic {Try}Parse methods. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single call private static bool TryParse(ReadOnlySpan 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) + 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)); } - value = value.TrimStart(); - if (value.Length == 0) - { - if (throwOnFailure) - { - throw new ArgumentException(SR.Arg_MustContainEnumInfo, nameof(value)); - } - result = default; - return false; - } - - int intResult; - uint uintResult; - bool parsed; + Unsafe.SkipInit(out result); RuntimeType rt = (RuntimeType)typeof(TEnum); + Type underlyingType = typeof(TEnum).GetEnumUnderlyingType(); + + if (underlyingType == typeof(int)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(uint)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(long)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(ulong)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(byte)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(sbyte)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(short)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(ushort)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(float)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(double)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(char)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(nint)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(nuint)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); + if (underlyingType == typeof(bool)) return TryParseByValueOrName(rt, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); - switch (Type.GetTypeCode(typeof(TEnum))) + if (throwOnFailure) { - case TypeCode.SByte: - parsed = TryParseInt32Enum(rt, value, sbyte.MinValue, sbyte.MaxValue, ignoreCase, throwOnFailure, TypeCode.SByte, out intResult); - sbyte sbyteResult = (sbyte)intResult; - result = Unsafe.As(ref sbyteResult); - return parsed; - - case TypeCode.Int16: - parsed = TryParseInt32Enum(rt, value, short.MinValue, short.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int16, out intResult); - short shortResult = (short)intResult; - result = Unsafe.As(ref shortResult); - return parsed; - - case TypeCode.Int32: - parsed = TryParseInt32Enum(rt, value, int.MinValue, int.MaxValue, ignoreCase, throwOnFailure, TypeCode.Int32, out intResult); - result = Unsafe.As(ref intResult); - return parsed; - - case TypeCode.Byte: - parsed = TryParseUInt32Enum(rt, value, byte.MaxValue, ignoreCase, throwOnFailure, TypeCode.Byte, out uintResult); - byte byteResult = (byte)uintResult; - result = Unsafe.As(ref byteResult); - return parsed; - - case TypeCode.UInt16: - parsed = TryParseUInt32Enum(rt, value, ushort.MaxValue, ignoreCase, throwOnFailure, TypeCode.UInt16, out uintResult); - ushort ushortResult = (ushort)uintResult; - result = Unsafe.As(ref ushortResult); - return parsed; - - case TypeCode.UInt32: - parsed = TryParseUInt32Enum(rt, value, uint.MaxValue, ignoreCase, throwOnFailure, TypeCode.UInt32, out uintResult); - result = Unsafe.As(ref uintResult); - return parsed; - - case TypeCode.Int64: - parsed = TryParseInt64Enum(rt, value, ignoreCase, throwOnFailure, out long longResult); - result = Unsafe.As(ref longResult); - return parsed; - - case TypeCode.UInt64: - parsed = TryParseUInt64Enum(rt, value, ignoreCase, throwOnFailure, out ulong ulongResult); - result = Unsafe.As(ref ulongResult); - return parsed; - - default: - parsed = TryParseRareEnum(rt, value, ignoreCase, throwOnFailure, out object? objectResult); - result = parsed ? (TEnum)objectResult! : default; - return parsed; + throw CreateUnknownEnumTypeException(); } + result = default; + return false; } - /// Tries to parse the value of an enum with known underlying types that fit in an Int32 (Int32, Int16, and SByte). - private static bool TryParseInt32Enum( - RuntimeType enumType, ReadOnlySpan value, int minInclusive, int maxInclusive, bool ignoreCase, bool throwOnFailure, TypeCode type, out int result) + /// Core implementation for all {Try}Parse methods, both generic and non-generic, parsing either by value or by name. + private static unsafe bool TryParseByValueOrName( + RuntimeType enumType, ReadOnlySpan value, bool ignoreCase, bool throwOnFailure, out TUnderlyingValue result) + where TUnderlyingValue : struct, INumber, IBitwiseOperators, IMinMaxValue { - Debug.Assert( - enumType.GetEnumUnderlyingType() == typeof(sbyte) || - enumType.GetEnumUnderlyingType() == typeof(short) || - enumType.GetEnumUnderlyingType() == typeof(int)); - - Number.ParsingStatus status = default; - if (StartsNumber(value[0])) + if (!value.IsEmpty) { - status = Number.TryParseInt32IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result); - if (status == Number.ParsingStatus.OK) + char c = value[0]; + if (char.IsWhiteSpace(c)) { - if ((uint)(result - minInclusive) <= (uint)(maxInclusive - minInclusive)) + value = value.TrimStart(); + if (value.IsEmpty) { - return true; + goto ParseFailure; } - status = Number.ParsingStatus.Overflow; + c = value[0]; } - } - if (status == Number.ParsingStatus.Overflow) - { - if (throwOnFailure) + if (!char.IsAsciiDigit(c) && c != '-' && c != '+') { - Number.ThrowOverflowException(type); + return TryParseByName(enumType, value, ignoreCase, throwOnFailure, out result); } - } - else if (TryParseByName(enumType, value, ignoreCase, throwOnFailure, out ulong ulongResult)) - { - result = (int)ulongResult; - Debug.Assert(result >= minInclusive && result <= maxInclusive); - return true; - } - result = 0; - return false; - } - - /// Tries to parse the value of an enum with known underlying types that fit in a UInt32 (UInt32, UInt16, and Byte). - private static bool TryParseUInt32Enum(RuntimeType enumType, ReadOnlySpan value, uint maxInclusive, bool ignoreCase, bool throwOnFailure, TypeCode type, out uint result) - { - Debug.Assert( - enumType.GetEnumUnderlyingType() == typeof(byte) || - enumType.GetEnumUnderlyingType() == typeof(ushort) || - enumType.GetEnumUnderlyingType() == typeof(uint)); + NumberFormatInfo numberFormat = CultureInfo.InvariantCulture.NumberFormat; + const NumberStyles NumberStyle = NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite; - Number.ParsingStatus status = default; - if (StartsNumber(value[0])) - { - status = Number.TryParseUInt32IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result); - if (status == Number.ParsingStatus.OK) + Number.ParsingStatus status; + if (typeof(TUnderlyingValue) == typeof(int)) { - if (result <= maxInclusive) + Unsafe.SkipInit(out result); + status = Number.TryParseInt32IntegerStyle(value, NumberStyle, numberFormat, out Unsafe.As(ref result)); + if (status == Number.ParsingStatus.OK) { return true; } - - status = Number.ParsingStatus.Overflow; } - } - - if (status == Number.ParsingStatus.Overflow) - { - if (throwOnFailure) + else if (typeof(TUnderlyingValue) == typeof(uint)) { - Number.ThrowOverflowException(type); + Unsafe.SkipInit(out result); + status = Number.TryParseUInt32IntegerStyle(value, NumberStyle, numberFormat, out Unsafe.As(ref result)); + if (status == Number.ParsingStatus.OK) + { + return true; + } } - } - else if (TryParseByName(enumType, value, ignoreCase, throwOnFailure, out ulong ulongResult)) - { - result = (uint)ulongResult; - Debug.Assert(result <= maxInclusive); - return true; - } - - result = 0; - return false; - } - - /// Tries to parse the value of an enum with Int64 as the underlying type. - private static bool TryParseInt64Enum(RuntimeType enumType, ReadOnlySpan value, bool ignoreCase, bool throwOnFailure, out long result) - { - Debug.Assert(enumType.GetEnumUnderlyingType() == typeof(long)); - - Number.ParsingStatus status = default; - if (StartsNumber(value[0])) - { - status = Number.TryParseInt64IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result); - if (status == Number.ParsingStatus.OK) + else if (typeof(TUnderlyingValue) == typeof(long)) { - return true; + Unsafe.SkipInit(out result); + status = Number.TryParseInt64IntegerStyle(value, NumberStyle, numberFormat, out Unsafe.As(ref result)); + if (status == Number.ParsingStatus.OK) + { + return true; + } } - } - - if (status == Number.ParsingStatus.Overflow) - { - if (throwOnFailure) + else if (typeof(TUnderlyingValue) == typeof(ulong)) { - Number.ThrowOverflowException(TypeCode.Int64); + Unsafe.SkipInit(out result); + status = Number.TryParseUInt64IntegerStyle(value, NumberStyle, numberFormat, out Unsafe.As(ref result)); + if (status == Number.ParsingStatus.OK) + { + return true; + } } - } - else if (TryParseByName(enumType, value, ignoreCase, throwOnFailure, out ulong ulongResult)) - { - result = (long)ulongResult; - return true; - } - - result = 0; - return false; - } - - /// Tries to parse the value of an enum with UInt64 as the underlying type. - private static bool TryParseUInt64Enum(RuntimeType enumType, ReadOnlySpan value, bool ignoreCase, bool throwOnFailure, out ulong result) - { - Debug.Assert(enumType.GetEnumUnderlyingType() == typeof(ulong)); - - Number.ParsingStatus status = default; - if (StartsNumber(value[0])) - { - status = Number.TryParseUInt64IntegerStyle(value, NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture.NumberFormat, out result); - if (status == Number.ParsingStatus.OK) + else if (typeof(TUnderlyingValue) == typeof(byte) || typeof(TUnderlyingValue) == typeof(ushort)) { - return true; + status = Number.TryParseUInt32IntegerStyle(value, NumberStyle, numberFormat, out uint uint32result); + if (status == Number.ParsingStatus.OK) + { + if (uint32result <= uint.CreateTruncating(TUnderlyingValue.MaxValue)) + { + result = TUnderlyingValue.CreateTruncating(uint32result); + return true; + } + status = Number.ParsingStatus.Overflow; + } } - } - - if (status == Number.ParsingStatus.Overflow) - { - if (throwOnFailure) + else if (typeof(TUnderlyingValue) == typeof(sbyte) || typeof(TUnderlyingValue) == typeof(short)) { - Number.ThrowOverflowException(TypeCode.UInt64); + status = Number.TryParseInt32IntegerStyle(value, NumberStyle, numberFormat, out int int32result); + if (status == Number.ParsingStatus.OK) + { + if (int32result >= int.CreateTruncating(TUnderlyingValue.MinValue) && int32result <= int.CreateTruncating(TUnderlyingValue.MaxValue)) + { + result = TUnderlyingValue.CreateTruncating(int32result); + return true; + } + status = Number.ParsingStatus.Overflow; + } } - } - else if (TryParseByName(enumType, value, ignoreCase, throwOnFailure, out result)) - { - return true; - } - - result = 0; - return false; - } - - /// Tries to parse the value of an enum with an underlying type that can't be expressed in C# (e.g. char, bool, double, etc.) - private static bool TryParseRareEnum(RuntimeType enumType, ReadOnlySpan value, bool ignoreCase, bool throwOnFailure, [NotNullWhen(true)] out object? result) - { - Debug.Assert( - enumType.GetEnumUnderlyingType() != typeof(sbyte) && - enumType.GetEnumUnderlyingType() != typeof(byte) && - enumType.GetEnumUnderlyingType() != typeof(short) && - enumType.GetEnumUnderlyingType() != typeof(ushort) && - enumType.GetEnumUnderlyingType() != typeof(int) && - enumType.GetEnumUnderlyingType() != typeof(uint) && - enumType.GetEnumUnderlyingType() != typeof(long) && - enumType.GetEnumUnderlyingType() != typeof(ulong), - "Should only be used when parsing enums with rare underlying types, those that can't be expressed in C#."); - - if (StartsNumber(value[0])) - { - Type underlyingType = GetUnderlyingType(enumType); - try + else { - result = ToObject(enumType, Convert.ChangeType(value.ToString(), underlyingType, CultureInfo.InvariantCulture)!); - return true; + // Rare types not expressible in C# + Type underlyingType = GetUnderlyingType(enumType); + try + { + result = (TUnderlyingValue)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 + } } - catch (FormatException) + + if (status != Number.ParsingStatus.Overflow) { - // We need to Parse this as a String instead. There are cases - // when you tlbimp enums that can have values of the form "3D". + return TryParseByName(enumType, value, ignoreCase, throwOnFailure, out result); } - catch when (!throwOnFailure) + + if (throwOnFailure) { - result = null; - return false; + Number.ThrowOverflowException(Type.GetTypeCode(typeof(TUnderlyingValue))); } } - if (TryParseByName(enumType, value, ignoreCase, throwOnFailure, out ulong ulongResult)) + ParseFailure: + if (throwOnFailure) { - try - { - result = ToObject(enumType, ulongResult); - return true; - } - catch when (!throwOnFailure) { } + ThrowInvalidEmptyParseArgument(); } - result = null; + result = default; return false; } - private static bool TryParseByName(RuntimeType enumType, ReadOnlySpan value, bool ignoreCase, bool throwOnFailure, out ulong result) + /// Handles just the name parsing portion of . + private static bool TryParseByName(RuntimeType enumType, ReadOnlySpan value, bool ignoreCase, bool throwOnFailure, out TUnderlyingValue result) + where TUnderlyingValue : struct, INumber, IBitwiseOperators { ReadOnlySpan originalValue = value; // Find the field. Let's assume that these are always static classes because the class is an enum. - EnumInfo enumInfo = GetEnumInfo(enumType); + EnumInfo enumInfo = GetEnumInfo(enumType); string[] enumNames = enumInfo.Names; - ulong[] enumValues = enumInfo.Values; + TUnderlyingValue[] enumValues = enumInfo.Values; bool parsed = true; - ulong localResult = 0; + TUnderlyingValue localResult = default; while (value.Length > 0) { // Find the next separator. @@ -1024,7 +1103,7 @@ private static bool TryParseByName(RuntimeType enumType, ReadOnlySpan valu { for (int i = 0; i < enumNames.Length; i++) { - if (subvalue.EqualsOrdinal(enumNames[i])) + if (subvalue.SequenceEqual(enumNames[i])) { localResult |= enumValues[i]; success = true; @@ -1051,163 +1130,71 @@ private static bool TryParseByName(RuntimeType enumType, ReadOnlySpan valu throw new ArgumentException(SR.Format(SR.Arg_EnumValueNotFound, originalValue.ToString())); } - result = 0; + result = default; return false; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool StartsNumber(char c) => char.IsAsciiDigit(c) || c == '-' || c == '+'; - - public static object ToObject(Type enumType, object value) + /// + /// Silently convert the to a from the other base types for enum without + /// throwing an exception (other than for an unknown enum type). + /// + /// This is needed since the Convert functions do overflow checks. + internal static ulong ToUInt64(object value) { - ArgumentNullException.ThrowIfNull(value); - - // Delegate rest of error checking to the other functions - TypeCode typeCode = Convert.GetTypeCode(value); - - return typeCode switch + switch (Convert.GetTypeCode(value)) { - TypeCode.Int32 => ToObject(enumType, (int)value), - TypeCode.SByte => ToObject(enumType, (sbyte)value), - TypeCode.Int16 => ToObject(enumType, (short)value), - TypeCode.Int64 => ToObject(enumType, (long)value), - TypeCode.UInt32 => ToObject(enumType, (uint)value), - TypeCode.Byte => ToObject(enumType, (byte)value), - TypeCode.UInt16 => ToObject(enumType, (ushort)value), - TypeCode.UInt64 => ToObject(enumType, (ulong)value), - TypeCode.Char => ToObject(enumType, (char)value), - TypeCode.Boolean => ToObject(enumType, (bool)value), - _ => throw new ArgumentException(SR.Arg_MustBeEnumBaseTypeOrEnum, nameof(value)), + case TypeCode.SByte: return (ulong)(sbyte)value; + case TypeCode.Byte: return (byte)value; + case TypeCode.Boolean: return (bool)value ? 1UL : 0UL; + case TypeCode.Int16: return (ulong)(short)value; + case TypeCode.UInt16: return (ushort)value; + case TypeCode.Char: return (char)value; + case TypeCode.UInt32: return (uint)value; + case TypeCode.Int32: return (ulong)(int)value; + case TypeCode.UInt64: return (ulong)value; + case TypeCode.Int64: return (ulong)(long)value; }; - } - - public static string Format(Type enumType, object value, [StringSyntax(StringSyntaxAttribute.EnumFormat)] string format) - { - ArgumentNullException.ThrowIfNull(value); - ArgumentNullException.ThrowIfNull(format); - - RuntimeType rtType = ValidateRuntimeType(enumType); - // If the value is an Enum then we need to extract the underlying value from it - Type valueType = value.GetType(); - if (valueType.IsEnum) + if (value is not null) { - if (!valueType.IsEquivalentTo(enumType)) - throw new ArgumentException(SR.Format(SR.Arg_EnumAndObjectMustBeSameType, valueType, enumType)); - - if (format.Length != 1) + Type valueType = value.GetType(); + if (valueType.IsEnum) { - // all acceptable format string are of length 1 - throw new FormatException(SR.Format_InvalidEnumFormatSpecification); + valueType = valueType.GetEnumUnderlyingType(); } - return ((Enum)value).ToString(format); - } - // The value must be of the same type as the Underlying type of the Enum - Type underlyingType = GetUnderlyingType(enumType); - if (valueType != underlyingType) - { - throw new ArgumentException(SR.Format(SR.Arg_EnumFormatUnderlyingTypeAndObjectMustBeSameType, valueType, underlyingType)); + if (valueType == typeof(nint)) return (ulong)(nint)value; + if (valueType == typeof(nuint)) return (nuint)value; } - if (format.Length == 1) - { - switch (format[0]) - { - case 'G': - case 'g': - return InternalFormat(rtType, ToUInt64(value)) ?? value.ToString()!; - - case 'D': - case 'd': - return value.ToString()!; - - case 'X': - case 'x': - return ValueToHexString(value); - - case 'F': - case 'f': - return InternalFlagsFormat(rtType, ToUInt64(value)) ?? value.ToString()!; - } - } - - throw new FormatException(SR.Format_InvalidEnumFormatSpecification); + throw CreateUnknownEnumTypeException(); } - #endregion - #region Private Methods + /// Gets a boxed underlying value of this enum. internal object GetValue() { ref byte data = ref this.GetRawData(); - return (InternalGetCorElementType()) switch + return InternalGetCorElementType() switch { CorElementType.ELEMENT_TYPE_I1 => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_U1 => data, - CorElementType.ELEMENT_TYPE_BOOLEAN => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_I2 => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_U2 => Unsafe.As(ref data), - CorElementType.ELEMENT_TYPE_CHAR => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_I4 => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_U4 => Unsafe.As(ref data), - CorElementType.ELEMENT_TYPE_R4 => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_I8 => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_U8 => Unsafe.As(ref data), + CorElementType.ELEMENT_TYPE_R4 => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_R8 => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_I => Unsafe.As(ref data), CorElementType.ELEMENT_TYPE_U => Unsafe.As(ref data), - _ => throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType), + CorElementType.ELEMENT_TYPE_CHAR => Unsafe.As(ref data), + CorElementType.ELEMENT_TYPE_BOOLEAN => Unsafe.As(ref data), + _ => throw CreateUnknownEnumTypeException(), }; } - private ulong ToUInt64() - { - ref byte data = ref this.GetRawData(); - switch (InternalGetCorElementType()) - { - case CorElementType.ELEMENT_TYPE_I1: - return (ulong)Unsafe.As(ref data); - case CorElementType.ELEMENT_TYPE_U1: - return data; - case CorElementType.ELEMENT_TYPE_BOOLEAN: - return data != 0 ? 1UL : 0UL; - case CorElementType.ELEMENT_TYPE_I2: - return (ulong)Unsafe.As(ref data); - case CorElementType.ELEMENT_TYPE_U2: - case CorElementType.ELEMENT_TYPE_CHAR: - return Unsafe.As(ref data); - case CorElementType.ELEMENT_TYPE_I4: -#if TARGET_32BIT - case CorElementType.ELEMENT_TYPE_I: -#endif - return (ulong)Unsafe.As(ref data); - case CorElementType.ELEMENT_TYPE_U4: -#if TARGET_32BIT - case CorElementType.ELEMENT_TYPE_U: -#endif - case CorElementType.ELEMENT_TYPE_R4: - return Unsafe.As(ref data); - case CorElementType.ELEMENT_TYPE_I8: -#if TARGET_64BIT - case CorElementType.ELEMENT_TYPE_I: -#endif - return (ulong)Unsafe.As(ref data); - case CorElementType.ELEMENT_TYPE_U8: -#if TARGET_64BIT - case CorElementType.ELEMENT_TYPE_U: -#endif - case CorElementType.ELEMENT_TYPE_R8: - return Unsafe.As(ref data); - default: - Debug.Fail("Unknown enum underlying type"); - return 0; - } - } - - #endregion - - #region Object Overrides - + /// public override bool Equals([NotNullWhen(true)] object? obj) { if (obj is null) @@ -1228,10 +1215,12 @@ public override bool Equals([NotNullWhen(true)] object? obj) case CorElementType.ELEMENT_TYPE_U1: case CorElementType.ELEMENT_TYPE_BOOLEAN: return pThisValue == pOtherValue; + case CorElementType.ELEMENT_TYPE_I2: case CorElementType.ELEMENT_TYPE_U2: case CorElementType.ELEMENT_TYPE_CHAR: return Unsafe.As(ref pThisValue) == Unsafe.As(ref pOtherValue); + case CorElementType.ELEMENT_TYPE_I4: case CorElementType.ELEMENT_TYPE_U4: #if TARGET_32BIT @@ -1240,6 +1229,7 @@ public override bool Equals([NotNullWhen(true)] object? obj) #endif case CorElementType.ELEMENT_TYPE_R4: return Unsafe.As(ref pThisValue) == Unsafe.As(ref pOtherValue); + case CorElementType.ELEMENT_TYPE_I8: case CorElementType.ELEMENT_TYPE_U8: #if TARGET_64BIT @@ -1248,50 +1238,41 @@ public override bool Equals([NotNullWhen(true)] object? obj) #endif case CorElementType.ELEMENT_TYPE_R8: return Unsafe.As(ref pThisValue) == Unsafe.As(ref pOtherValue); + default: Debug.Fail("Unknown enum underlying type"); return false; } } + /// 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 + return InternalGetCorElementType() switch { CorElementType.ELEMENT_TYPE_I1 => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U1 => data.GetHashCode(), - CorElementType.ELEMENT_TYPE_BOOLEAN => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_I2 => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U2 => Unsafe.As(ref data).GetHashCode(), - CorElementType.ELEMENT_TYPE_CHAR => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_I4 => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U4 => Unsafe.As(ref data).GetHashCode(), - CorElementType.ELEMENT_TYPE_R4 => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_I8 => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U8 => Unsafe.As(ref data).GetHashCode(), + CorElementType.ELEMENT_TYPE_R4 => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_R8 => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_I => Unsafe.As(ref data).GetHashCode(), CorElementType.ELEMENT_TYPE_U => Unsafe.As(ref data).GetHashCode(), - _ => throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType), + CorElementType.ELEMENT_TYPE_CHAR => Unsafe.As(ref data).GetHashCode(), + CorElementType.ELEMENT_TYPE_BOOLEAN => Unsafe.As(ref data).GetHashCode(), + _ => throw CreateUnknownEnumTypeException(), }; } - public override string ToString() - { - // Returns the value in a human readable format. For PASCAL style enums who's value maps directly the name of the field is returned. - // For PASCAL style enums who's values do not map directly the decimal value of the field is returned. - // For BitFlags (indicated by the Flags custom attribute): If for each bit that is set in the value there is a corresponding constant - // (a pure power of 2), then the OR string (ie "Red, Yellow") is returned. Otherwise, if the value is zero or if you can't create a string that consists of - // pure powers of 2 OR-ed together, you return a hex value - - // Try to see if its one of the enum values, then we return a String back else the value - return InternalFormat((RuntimeType)GetType(), ToUInt64()) ?? ValueToString(); - } - + /// public int CompareTo(object? target) { if (target == this) @@ -1310,54 +1291,93 @@ public int CompareTo(object? target) { case CorElementType.ELEMENT_TYPE_I1: return Unsafe.As(ref pThisValue).CompareTo(Unsafe.As(ref pTargetValue)); + case CorElementType.ELEMENT_TYPE_U1: case CorElementType.ELEMENT_TYPE_BOOLEAN: return pThisValue.CompareTo(pTargetValue); + case CorElementType.ELEMENT_TYPE_I2: return Unsafe.As(ref pThisValue).CompareTo(Unsafe.As(ref pTargetValue)); + case CorElementType.ELEMENT_TYPE_U2: case CorElementType.ELEMENT_TYPE_CHAR: return Unsafe.As(ref pThisValue).CompareTo(Unsafe.As(ref pTargetValue)); + case CorElementType.ELEMENT_TYPE_I4: #if TARGET_32BIT case CorElementType.ELEMENT_TYPE_I: #endif return Unsafe.As(ref pThisValue).CompareTo(Unsafe.As(ref pTargetValue)); + case CorElementType.ELEMENT_TYPE_U4: #if TARGET_32BIT case CorElementType.ELEMENT_TYPE_U: #endif return Unsafe.As(ref pThisValue).CompareTo(Unsafe.As(ref pTargetValue)); + case CorElementType.ELEMENT_TYPE_I8: #if TARGET_64BIT case CorElementType.ELEMENT_TYPE_I: #endif return Unsafe.As(ref pThisValue).CompareTo(Unsafe.As(ref pTargetValue)); + case CorElementType.ELEMENT_TYPE_U8: #if TARGET_64BIT case CorElementType.ELEMENT_TYPE_U: #endif return Unsafe.As(ref pThisValue).CompareTo(Unsafe.As(ref pTargetValue)); + case CorElementType.ELEMENT_TYPE_R4: return Unsafe.As(ref pThisValue).CompareTo(Unsafe.As(ref pTargetValue)); + case CorElementType.ELEMENT_TYPE_R8: return Unsafe.As(ref pThisValue).CompareTo(Unsafe.As(ref pTargetValue)); + default: Debug.Fail("Unknown enum underlying type"); return 0; } } - #endregion - #region IFormattable - [Obsolete("The provider argument is not used. Use ToString(String) instead.")] - public string ToString([StringSyntax(StringSyntaxAttribute.EnumFormat)] string? format, IFormatProvider? provider) + /// Converts the value of this instance to its equivalent string representation. + /// The string representation of the value of this instance. + public override string ToString() { - return ToString(format); + 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(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_U1 => ToStringInlined(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_I2 => ToString(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_U2 => ToString(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_I4 => ToStringInlined(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_U4 => ToString(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_I8 => ToString(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_U8 => ToString(enumType, ref rawData), + _ => HandleRareTypes(enumType, ref rawData) + }; + + [MethodImpl(MethodImplOptions.NoInlining)] + static string HandleRareTypes(RuntimeType enumType, ref byte rawData) => + InternalGetCorElementType(enumType) switch + { + CorElementType.ELEMENT_TYPE_R4 => ToString(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_R8 => ToString(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_I => ToString(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_U => ToString(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_CHAR => ToString(enumType, ref rawData), + CorElementType.ELEMENT_TYPE_BOOLEAN => ToStringBool(enumType, null, ref rawData), + _ => throw CreateUnknownEnumTypeException(), + }; } - #endregion - #region Public Methods + /// Converts the value of this instance to its equivalent string representation using the specified format. + /// A format string. + /// The string representation of the value of this instance as specified by . + /// contains an invalid specification. + /// equals "X" or "x", but the enumeration type is unknown. public string ToString([StringSyntax(StringSyntaxAttribute.EnumFormat)] string? format) { if (string.IsNullOrEmpty(format)) @@ -1367,133 +1387,872 @@ public string ToString([StringSyntax(StringSyntaxAttribute.EnumFormat)] string? if (format.Length == 1) { - switch (format[0]) + char formatChar = format[0]; + RuntimeType enumType = (RuntimeType)GetType(); + ref byte rawData = ref this.GetRawData(); + return InternalGetCorElementType() switch { - case 'G': - case 'g': - return ToString(); + // Inlined for the most common base types + CorElementType.ELEMENT_TYPE_I1 => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U1 => ToStringInlined(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_I2 => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U2 => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_I4 => ToStringInlined(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U4 => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_I8 => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U8 => ToString(enumType, formatChar, ref rawData), + _ => HandleRareTypes(enumType, formatChar, ref rawData) + }; + } - case 'D': - case 'd': - return ValueToString(); + throw CreateInvalidFormatSpecifierException(); - case 'X': - case 'x': - return ValueToHexString(); + [MethodImpl(MethodImplOptions.NoInlining)] + static string HandleRareTypes(RuntimeType enumType, char formatChar, ref byte rawData) => + InternalGetCorElementType(enumType) switch + { + CorElementType.ELEMENT_TYPE_R4 => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_R8 => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_I => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_CHAR => ToString(enumType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_BOOLEAN => ToStringBool(enumType, formatChar.ToString(), ref rawData), + _ => throw CreateUnknownEnumTypeException(), + }; + } - case 'F': - case 'f': - return InternalFlagsFormat((RuntimeType)GetType(), ToUInt64()) ?? ValueToString(); - } - } + /// This method overload is obsolete; use . + [Obsolete("The provider argument is not used. Use ToString() instead.")] + public string ToString(IFormatProvider? provider) => + ToString(); + + /// This method overload is obsolete; use . + [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(RuntimeType enumType, ref byte rawData) + where TUnderlyingValue : struct, INumber, IBitwiseOperators => + ToStringInlined(enumType, ref rawData); + + /// Converts the value of an enum to its equivalent string representation using the default format. + /// The type of the underlying value. + /// The enum type. + /// A reference to the enum's value. + /// The string representation of the value of this instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // used for most important types at most important call sites + private static string ToStringInlined(RuntimeType enumType, ref byte rawData) + where TUnderlyingValue : struct, INumber, IBitwiseOperators + { + TUnderlyingValue value = Unsafe.As(ref rawData); + + EnumInfo enumInfo = GetEnumInfo(enumType); + string? result = enumInfo.HasFlagsAttribute ? + FormatFlagNames(enumInfo, value) : + GetNameInlined(enumInfo, value); - throw new FormatException(SR.Format_InvalidEnumFormatSpecification); + return result ?? value.ToString()!; } - [Obsolete("The provider argument is not used. Use ToString() instead.")] - public string ToString(IFormatProvider? provider) + [MethodImpl(MethodImplOptions.NoInlining)] // avoid bloating call sites for underlying types and/or call sites that aren't perf critical + private static string ToString(RuntimeType enumType, char format, ref byte rawData) + where TUnderlyingValue : struct, INumber, IBitwiseOperators, IMinMaxValue => + ToStringInlined(enumType, format, ref rawData); + + /// Converts the value of an enum to its equivalent string representation using the default format. + /// The type of the underlying value. + /// The enum type. + /// A format string. + /// A reference to the enum's value. + /// The string representation of the value of this instance as specified by . + [MethodImpl(MethodImplOptions.AggressiveInlining)] // used for most important types at most important call sites + private static string ToStringInlined(RuntimeType enumType, char format, ref byte rawData) + where TUnderlyingValue : struct, INumber, IBitwiseOperators, IMinMaxValue { - return ToString(); + TUnderlyingValue value = Unsafe.As(ref rawData); + + string? result; + switch (format | 0x20) + { + case 'g': + EnumInfo enumInfo = GetEnumInfo(enumType); + result = enumInfo.HasFlagsAttribute ? FormatFlagNames(enumInfo, value) : GetNameInlined(enumInfo, value); + if (result is null) + { + goto case 'd'; + } + break; + + case 'd': + result = value.ToString()!; + break; + + case 'x': + result = FormatNumberAsHex(ref rawData); + break; + + case 'f': + result = FormatFlagNames(GetEnumInfo(enumType), value); + if (result is null) + { + goto case 'd'; + } + break; + + default: + throw CreateInvalidFormatSpecifierException(); + }; + + return result; } - #endregion + /// ToString implementation to use for enums with bool as an underlying type. + /// + /// bool is the only underlying type that doesn't fit our scheme using INumber{T}, since it doesn't implement the numeric interfaces. + /// We can mostly make everything work for it by substituting byte, but for some rendering purposes we need to use a dedicated implementation. + /// Performance here is not an issue; these can't be expressed in C#, and serve little purpose, so bool-based enums are exceedingly rare. + /// This exists purely for compat. + /// + private static string ToStringBool(RuntimeType enumType, string? format, ref byte rawData) + { + if (string.IsNullOrEmpty(format)) + { + format = "g"; + } + + byte byteValue = rawData; + bool boolValue = byteValue != 0; + + string? result; + switch (format[0] | 0x20) + { + case 'g': + EnumInfo enumInfo = GetEnumInfo(enumType); + result = enumInfo.HasFlagsAttribute ? FormatFlagNames(enumInfo, byteValue) : GetName(enumInfo, byteValue); + if (result is null) + { + goto case 'd'; + } + break; + + case 'd': + return boolValue.ToString(); - #region IConvertible - public TypeCode GetTypeCode() + case 'x': + return boolValue ? "01" : "00"; + + case 'f': + result = FormatFlagNames(GetEnumInfo(enumType), byteValue); + if (result is null) + { + goto case 'd'; + } + break; + + default: + throw CreateInvalidFormatSpecifierException(); + } + + return result; + } + + /// TryFormat implementation to use for enums with bool as an underlying type. + /// See remarks on . + private static bool TryFormatBool(RuntimeType enumType, bool boolValue, Span destination, out int charsWritten, ReadOnlySpan format) { - return (InternalGetCorElementType()) switch + byte byteValue = boolValue ? (byte)1 : (byte)0; + + string result = ToStringBool(enumType, format.ToString(), ref byteValue); + if (result.TryCopyTo(destination)) { - CorElementType.ELEMENT_TYPE_I1 => TypeCode.SByte, - CorElementType.ELEMENT_TYPE_U1 => TypeCode.Byte, - CorElementType.ELEMENT_TYPE_BOOLEAN => TypeCode.Boolean, - CorElementType.ELEMENT_TYPE_I2 => TypeCode.Int16, - CorElementType.ELEMENT_TYPE_U2 => TypeCode.UInt16, - CorElementType.ELEMENT_TYPE_CHAR => TypeCode.Char, - CorElementType.ELEMENT_TYPE_I4 => TypeCode.Int32, - CorElementType.ELEMENT_TYPE_U4 => TypeCode.UInt32, - CorElementType.ELEMENT_TYPE_I8 => TypeCode.Int64, - CorElementType.ELEMENT_TYPE_U8 => TypeCode.UInt64, - _ => throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType), - }; + charsWritten = result.Length; + return true; + } + + charsWritten = 0; + return false; } - bool IConvertible.ToBoolean(IFormatProvider? provider) + /// Formats the data for the underlying value as hex into a new, fixed-length string. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe string FormatNumberAsHex(ref byte data) where TUnderlyingValue : struct { - return Convert.ToBoolean(GetValue()); + fixed (byte* ptr = &data) + { + return string.Create(Unsafe.SizeOf() * 2, (IntPtr)ptr, (destination, intptr) => + { + bool success = TryFormatNumberAsHex(ref *(byte*)intptr, destination, out int charsWritten); + Debug.Assert(success); + Debug.Assert(charsWritten == Unsafe.SizeOf() * 2); + }); + } + } + + /// Tries to format the data for the underlying value as hex into the destination span. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryFormatNumberAsHex(ref byte data, Span destination, out int charsWritten) where TUnderlyingValue : struct + { + if (Unsafe.SizeOf() * 2 <= destination.Length) + { + if (typeof(TUnderlyingValue) == typeof(byte) || + typeof(TUnderlyingValue) == typeof(sbyte)) + { + HexConverter.ToCharsBuffer(data, destination); + } + else if (typeof(TUnderlyingValue) == typeof(ushort) || + typeof(TUnderlyingValue) == typeof(short) || + typeof(TUnderlyingValue) == typeof(char)) + { + ushort value = Unsafe.As(ref data); + HexConverter.ToCharsBuffer((byte)(value >> 8), destination); + HexConverter.ToCharsBuffer((byte)value, destination, 2); + } + else if (typeof(TUnderlyingValue) == typeof(uint) || +#if TARGET_32BIT + typeof(TUnderlyingValue) == typeof(nuint) || + typeof(TUnderlyingValue) == typeof(nint) || +#endif + typeof(TUnderlyingValue) == typeof(int)) + { + uint value = Unsafe.As(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(TUnderlyingValue) == typeof(ulong) || +#if TARGET_64BIT + typeof(TUnderlyingValue) == typeof(nuint) || + typeof(TUnderlyingValue) == typeof(nint) || +#endif + typeof(TUnderlyingValue) == typeof(long)) + { + ulong value = Unsafe.As(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 = Unsafe.SizeOf() * 2; + return true; + } + + charsWritten = 0; + return false; } - char IConvertible.ToChar(IFormatProvider? provider) + /// Converts the specified value of a specified enumerated type to its equivalent string representation according to the specified format. + /// The enumeration type of the value to convert. + /// The value to convert. + /// The output format to use. + /// A string representation of . + /// The , , or parameter is null. + /// The parameter is not an type. + /// The is from an enumeration that differs in type from . + /// The type of is not an underlying type of . + /// contains an invalid value. + /// equals "X" or "x", but the enumeration type is unknown. + public static string Format(Type enumType, object value, [StringSyntax(StringSyntaxAttribute.EnumFormat)] string format) { - return Convert.ToChar(GetValue()); + 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(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U1 => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_I2 => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U2 => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_I4 => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U4 => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_I8 => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U8 => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_R4 => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_R8 => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_I => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_U => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_CHAR => ToString(rtType, formatChar, ref rawData), + CorElementType.ELEMENT_TYPE_BOOLEAN => ToStringBool(rtType, format, ref rawData), + _ => throw CreateUnknownEnumTypeException(), + }; + } + } + + throw CreateInvalidFormatSpecifierException(); } - sbyte IConvertible.ToSByte(IFormatProvider? provider) + /// Tries to format the value of the enum into the provided span of characters. + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in destination. + /// The format specifier. + /// An optional object that supplies culture-specific formatting information for destination. This is ignored. + /// if the formatting was successful; otherwise, . + bool ISpanFormattable.TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) { - return Convert.ToSByte(GetValue()); + RuntimeType enumType = (RuntimeType)GetType(); + ref byte rawData = ref this.GetRawData(); + CorElementType corElementType = InternalGetCorElementType(); + + if (format.IsEmpty) + { + return corElementType switch + { + CorElementType.ELEMENT_TYPE_I1 => TryFormatPrimitiveDefault(enumType, (sbyte)rawData, destination, out charsWritten), + CorElementType.ELEMENT_TYPE_U1 => TryFormatPrimitiveDefault(enumType, rawData, destination, out charsWritten), + CorElementType.ELEMENT_TYPE_I2 => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_U2 => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_I4 => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_U4 => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_I8 => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_U8 => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_R4 => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_R8 => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_I => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_U => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_CHAR => TryFormatPrimitiveDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten), + CorElementType.ELEMENT_TYPE_BOOLEAN => TryFormatBool(enumType, rawData != 0, destination, out charsWritten, format), + _ => throw CreateUnknownEnumTypeException(), + }; + } + else + { + return corElementType switch + { + CorElementType.ELEMENT_TYPE_I1 => TryFormatPrimitiveNonDefault(enumType, (sbyte)rawData, destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_U1 => TryFormatPrimitiveNonDefault(enumType, rawData, destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_I2 => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_U2 => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_I4 => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_U4 => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_I8 => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_U8 => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_R4 => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_R8 => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_I => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_U => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_CHAR => TryFormatPrimitiveNonDefault(enumType, Unsafe.As(ref rawData), destination, out charsWritten, format), + CorElementType.ELEMENT_TYPE_BOOLEAN => TryFormatBool(enumType, rawData != 0, destination, out charsWritten, format), + _ => throw CreateUnknownEnumTypeException(), + }; + } } - byte IConvertible.ToByte(IFormatProvider? provider) + /// Tries to format the value of the enumerated type instance into the provided span of characters. + /// + /// + /// The span into which to write the instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// 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". + /// if the formatting was successful; otherwise, if the destination span wasn't large enough to contain the formatted value. + /// The format parameter contains an invalid value. + public static unsafe bool TryFormat(TEnum value, Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.EnumFormat)] ReadOnlySpan format = default) where TEnum : struct, Enum { - return Convert.ToByte(GetValue()); + 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(rt, *(int*)&value, destination, out charsWritten); + if (underlyingType == typeof(uint)) return TryFormatPrimitiveDefault(rt, *(uint*)&value, destination, out charsWritten); + if (underlyingType == typeof(long)) return TryFormatPrimitiveDefault(rt, *(long*)&value, destination, out charsWritten); + if (underlyingType == typeof(ulong)) return TryFormatPrimitiveDefault(rt, *(ulong*)&value, destination, out charsWritten); + if (underlyingType == typeof(byte)) return TryFormatPrimitiveDefault(rt, *(byte*)&value, destination, out charsWritten); + if (underlyingType == typeof(sbyte)) return TryFormatPrimitiveDefault(rt, *(sbyte*)&value, destination, out charsWritten); + if (underlyingType == typeof(short)) return TryFormatPrimitiveDefault(rt, *(short*)&value, destination, out charsWritten); + if (underlyingType == typeof(ushort)) return TryFormatPrimitiveDefault(rt, *(ushort*)&value, destination, out charsWritten); + if (underlyingType == typeof(nint)) return TryFormatPrimitiveDefault(rt, *(nint*)&value, destination, out charsWritten); + if (underlyingType == typeof(nuint)) return TryFormatPrimitiveDefault(rt, *(nuint*)&value, destination, out charsWritten); + if (underlyingType == typeof(char)) return TryFormatPrimitiveDefault(rt, *(char*)&value, destination, out charsWritten); + if (underlyingType == typeof(float)) return TryFormatPrimitiveDefault(rt, *(float*)&value, destination, out charsWritten); + if (underlyingType == typeof(double)) return TryFormatPrimitiveDefault(rt, *(double*)&value, destination, out charsWritten); + if (underlyingType == typeof(bool)) return TryFormatBool(rt, *(bool*)&value, destination, out charsWritten, format: default); + } + else + { + if (underlyingType == typeof(int)) return TryFormatPrimitiveNonDefault(rt, *(int*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(uint)) return TryFormatPrimitiveNonDefault(rt, *(uint*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(long)) return TryFormatPrimitiveNonDefault(rt, *(long*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(ulong)) return TryFormatPrimitiveNonDefault(rt, *(ulong*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(byte)) return TryFormatPrimitiveNonDefault(rt, *(byte*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(sbyte)) return TryFormatPrimitiveNonDefault(rt, *(sbyte*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(short)) return TryFormatPrimitiveNonDefault(rt, *(short*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(ushort)) return TryFormatPrimitiveNonDefault(rt, *(ushort*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(nint)) return TryFormatPrimitiveNonDefault(rt, *(nint*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(nuint)) return TryFormatPrimitiveNonDefault(rt, *(nuint*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(char)) return TryFormatPrimitiveNonDefault(rt, *(char*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(float)) return TryFormatPrimitiveNonDefault(rt, *(float*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(double)) return TryFormatPrimitiveNonDefault(rt, *(double*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(bool)) return TryFormatBool(rt, *(bool*)&value, destination, out charsWritten, format); + } + + throw CreateUnknownEnumTypeException(); } - short IConvertible.ToInt16(IFormatProvider? provider) + /// Tries to format the value of the enumerated type instance into the provided span of characters. + /// + /// This is same as the implementation for . It is separated out as 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. + /// + [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 value, Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.EnumFormat)] ReadOnlySpan format = default) { - return Convert.ToInt16(GetValue()); + 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(rt, *(int*)&value, destination, out charsWritten); + if (underlyingType == typeof(uint)) return TryFormatPrimitiveDefault(rt, *(uint*)&value, destination, out charsWritten); + if (underlyingType == typeof(long)) return TryFormatPrimitiveDefault(rt, *(long*)&value, destination, out charsWritten); + if (underlyingType == typeof(ulong)) return TryFormatPrimitiveDefault(rt, *(ulong*)&value, destination, out charsWritten); + if (underlyingType == typeof(byte)) return TryFormatPrimitiveDefault(rt, *(byte*)&value, destination, out charsWritten); + if (underlyingType == typeof(sbyte)) return TryFormatPrimitiveDefault(rt, *(sbyte*)&value, destination, out charsWritten); + if (underlyingType == typeof(short)) return TryFormatPrimitiveDefault(rt, *(short*)&value, destination, out charsWritten); + if (underlyingType == typeof(ushort)) return TryFormatPrimitiveDefault(rt, *(ushort*)&value, destination, out charsWritten); + if (underlyingType == typeof(nint)) return TryFormatPrimitiveDefault(rt, *(nint*)&value, destination, out charsWritten); + if (underlyingType == typeof(nuint)) return TryFormatPrimitiveDefault(rt, *(nuint*)&value, destination, out charsWritten); + if (underlyingType == typeof(char)) return TryFormatPrimitiveDefault(rt, *(char*)&value, destination, out charsWritten); + if (underlyingType == typeof(float)) return TryFormatPrimitiveDefault(rt, *(float*)&value, destination, out charsWritten); + if (underlyingType == typeof(double)) return TryFormatPrimitiveDefault(rt, *(double*)&value, destination, out charsWritten); + if (underlyingType == typeof(bool)) return TryFormatBool(rt, *(bool*)&value, destination, out charsWritten, format: default); + } + else + { + if (underlyingType == typeof(int)) return TryFormatPrimitiveNonDefault(rt, *(int*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(uint)) return TryFormatPrimitiveNonDefault(rt, *(uint*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(long)) return TryFormatPrimitiveNonDefault(rt, *(long*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(ulong)) return TryFormatPrimitiveNonDefault(rt, *(ulong*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(byte)) return TryFormatPrimitiveNonDefault(rt, *(byte*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(sbyte)) return TryFormatPrimitiveNonDefault(rt, *(sbyte*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(short)) return TryFormatPrimitiveNonDefault(rt, *(short*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(ushort)) return TryFormatPrimitiveNonDefault(rt, *(ushort*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(nint)) return TryFormatPrimitiveNonDefault(rt, *(nint*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(nuint)) return TryFormatPrimitiveNonDefault(rt, *(nuint*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(char)) return TryFormatPrimitiveNonDefault(rt, *(char*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(float)) return TryFormatPrimitiveNonDefault(rt, *(float*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(double)) return TryFormatPrimitiveNonDefault(rt, *(double*)&value, destination, out charsWritten, format); + if (underlyingType == typeof(bool)) return TryFormatBool(rt, *(bool*)&value, destination, out charsWritten, format); + } + + throw CreateUnknownEnumTypeException(); } - ushort IConvertible.ToUInt16(IFormatProvider? provider) + /// Core implementation for when no format specifier was provided. + private static bool TryFormatPrimitiveDefault(RuntimeType enumType, TUnderlyingValue value, Span destination, out int charsWritten) + where TUnderlyingValue : struct, INumber, IBitwiseOperators, IMinMaxValue { - return Convert.ToUInt16(GetValue()); + EnumInfo enumInfo = GetEnumInfo(enumType); + + if (!enumInfo.HasFlagsAttribute) + { + if (GetNameInlined(enumInfo, value) is string enumName) + { + if (enumName.TryCopyTo(destination)) + { + charsWritten = enumName.Length; + return true; + } + + charsWritten = 0; + return false; + } + } + else + { + bool destinationIsTooSmall = false; + if (TryFormatFlagNames(enumInfo, value, destination, out charsWritten, ref destinationIsTooSmall) || destinationIsTooSmall) + { + return !destinationIsTooSmall; + } + } + + return value.TryFormat(destination, out charsWritten, format: default, provider: null); } - int IConvertible.ToInt32(IFormatProvider? provider) + /// Core implementation for when a format specifier was provided. + private static bool TryFormatPrimitiveNonDefault(RuntimeType enumType, TUnderlyingValue value, Span destination, out int charsWritten, ReadOnlySpan format) + where TUnderlyingValue : struct, INumber, IBitwiseOperators, IMinMaxValue { - return Convert.ToInt32(GetValue()); + Debug.Assert(!format.IsEmpty); + + if (format.Length == 1) + { + switch (format[0] | 0x20) + { + case 'g': + return TryFormatPrimitiveDefault(enumType, value, destination, out charsWritten); + + case 'd': + return value.TryFormat(destination, out charsWritten, format: default, provider: null); + + case 'x': + return TryFormatNumberAsHex(ref Unsafe.As(ref value), destination, out charsWritten); + + case 'f': + bool destinationIsTooSmall = false; + if (TryFormatFlagNames(GetEnumInfo(enumType), value, destination, out charsWritten, ref destinationIsTooSmall) || + destinationIsTooSmall) + { + return !destinationIsTooSmall; + } + goto case 'd'; + } + } + + throw CreateInvalidFormatSpecifierException(); } - uint IConvertible.ToUInt32(IFormatProvider? provider) + /// Tries to create a string representation of an enum as either a single constant name or multiple delimited constant names. + /// The formatted string if the value could be fully represented by enum constants, or else null. + private static string? FormatFlagNames(EnumInfo enumInfo, TUnderlyingValue resultValue) + where TUnderlyingValue : struct, INumber, IBitwiseOperators { - return Convert.ToUInt32(GetValue()); + string[] names = enumInfo.Names; + TUnderlyingValue[] 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 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(ref result.GetRawStringData(), result.Length)); + } + } + + return result; } - long IConvertible.ToInt64(IFormatProvider? provider) + /// Tries to format into a span a representation of an enum as either a single constant name or multiple delimited constant names. + /// + /// 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, is used to disambiguate the reason for the failure. + /// + private static bool TryFormatFlagNames(EnumInfo enumInfo, TUnderlyingValue resultValue, Span destination, out int charsWritten, ref bool isDestinationTooSmall) + where TUnderlyingValue : struct, INumber, IBitwiseOperators { - return Convert.ToInt64(GetValue()); + Debug.Assert(!isDestinationTooSmall); + + string[] names = enumInfo.Names; + TUnderlyingValue[] 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 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; } - ulong IConvertible.ToUInt64(IFormatProvider? provider) + /// + /// Calculates how many characters will be in a formatted value, where there are + /// names whose lengths all sum to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] // used twice, once from string-based and once from span-based code path + private static int GetMultipleEnumsFlagsFormatResultLength(int resultLength, int foundItemsCount) { - return Convert.ToUInt64(GetValue()); + 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); } - float IConvertible.ToSingle(IFormatProvider? provider) + /// Tries to find the single named constant for the specified value, or else the index where we left off searching after not finding it. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // used twice, once from string-based and once from span-based code path + private static string? GetSingleFlagsEnumNameForValue(TUnderlyingValue resultValue, string[] names, TUnderlyingValue[] values, out int index) + where TUnderlyingValue : struct, INumber { - return Convert.ToSingle(GetValue()); + // 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 == TUnderlyingValue.Zero) + { + index = 0; + return values.Length > 0 && values[0] == TUnderlyingValue.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; } - double IConvertible.ToDouble(IFormatProvider? provider) + /// Tries to compute the indices of all named constants that or together to equal the specified value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // used twice, once from string-based and once from span-based code path + private static bool TryFindFlagsNames(TUnderlyingValue resultValue, string[] names, TUnderlyingValue[] values, int index, Span foundItems, out int resultLength, out int foundItemsCount) + where TUnderlyingValue : struct, INumber, IBitwiseOperators { - return Convert.ToDouble(GetValue()); + // 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; + } + + TUnderlyingValue currentValue = values[index]; + if (index == 0 && currentValue == TUnderlyingValue.Zero) + { + break; + } + + if ((resultValue & currentValue) == currentValue) + { + resultValue &= ~currentValue; + foundItems[foundItemsCount] = index; + foundItemsCount++; + resultLength = checked(resultLength + names[index].Length); + if (resultValue == TUnderlyingValue.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 == TUnderlyingValue.Zero; } - decimal IConvertible.ToDecimal(IFormatProvider? provider) + /// Concatenates the names of the found items into the destination span. + /// The destination must have already been verified long enough to store the resulting data. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // used twice, once from string-based and once from span-based code path + private static void WriteMultipleFoundFlagsNames(string[] names, ReadOnlySpan foundItems, Span destination) { - return Convert.ToDecimal(GetValue()); + 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 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); } - DateTime IConvertible.ToDateTime(IFormatProvider? provider) + private static RuntimeType ValidateRuntimeType(Type enumType) { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "Enum", "DateTime")); + 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; } - object IConvertible.ToType(Type type, IFormatProvider? provider) + [DoesNotReturn] + private static void ThrowInvalidRuntimeType(Type enumType) => + throw (enumType is not RuntimeType ? + new ArgumentException(SR.Arg_MustBeType, nameof(enumType)) : + new ArgumentException(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 Exception CreateInvalidFormatSpecifierException() => + new FormatException(SR.Format_InvalidEnumFormatSpecification); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Exception 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, + CorElementType.ELEMENT_TYPE_BOOLEAN => TypeCode.Boolean, + // 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) { - return Convert.DefaultToType(this, type, provider); + 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); + case TypeCode.Single: return ToObject(enumType, BitConverter.SingleToInt32Bits((float)value)); + case TypeCode.Double: return ToObject(enumType, BitConverter.DoubleToInt64Bits((double)value)); + }; + + Type valueType = value.GetType(); + if (valueType.IsEnum) + { + valueType = valueType.GetEnumUnderlyingType(); + } + + if (valueType == typeof(nint)) ToObject(enumType, (nint)value); + if (valueType == typeof(nuint)) ToObject(enumType, (nuint)value); + + throw new ArgumentException(SR.Arg_MustBeEnumBaseTypeOrEnum, nameof(value)); } - #endregion - #region ToObject [CLSCompliant(false)] public static object ToObject(Type enumType, sbyte value) => InternalBoxEnum(ValidateRuntimeType(enumType), value); @@ -1522,29 +2281,17 @@ public static object ToObject(Type enumType, long value) => public static object ToObject(Type enumType, ulong value) => InternalBoxEnum(ValidateRuntimeType(enumType), unchecked((long)value)); - private static object ToObject(Type enumType, char value) => - InternalBoxEnum(ValidateRuntimeType(enumType), value); - - private static object ToObject(Type enumType, bool value) => - InternalBoxEnum(ValidateRuntimeType(enumType), value ? 1L : 0L); - - #endregion - - private static RuntimeType ValidateRuntimeType(Type enumType) + internal static bool AreSequentialFromZero(TUnderlyingValue[] values) where TUnderlyingValue : struct, INumber { - ArgumentNullException.ThrowIfNull(enumType); + for (int i = 0; i < values.Length; i++) + { + if (long.CreateTruncating(values[i]) != i) + { + return false; + } + } - if (enumType is not RuntimeType rtType) - throw new ArgumentException(SR.Arg_MustBeType, nameof(enumType)); - if (!rtType.IsActualEnum) - throw new ArgumentException(SR.Arg_MustBeEnum, nameof(enumType)); -#if NATIVEAOT - // Check for the unfortunate "typeof(Outer<>.InnerEnum)" corner case. - // https://github.com/dotnet/runtime/issues/7976 - if (enumType.ContainsGenericParameters) - throw new InvalidOperationException(SR.Format(SR.Arg_OpenType, enumType.ToString())); -#endif - return rtType; + return true; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index cc67fc169bced..99956b82abf55 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -112,10 +112,10 @@ public static string ExpandEnvironmentVariables(string name) public static string GetFolderPath(SpecialFolder folder, SpecialFolderOption option) { - if (!Enum.IsDefined(typeof(SpecialFolder), folder)) + if (!Enum.IsDefined(folder)) throw new ArgumentOutOfRangeException(nameof(folder), folder, SR.Format(SR.Arg_EnumIllegalVal, folder)); - if (option != SpecialFolderOption.None && !Enum.IsDefined(typeof(SpecialFolderOption), option)) + if (option != SpecialFolderOption.None && !Enum.IsDefined(option)) throw new ArgumentOutOfRangeException(nameof(option), option, SR.Format(SR.Arg_EnumIllegalVal, option)); return GetFolderPathCore(folder, option); diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 835816e0f38a4..5a99506447922 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -3410,10 +3410,21 @@ public bool AppendFormatted(T value) if (value is IFormattable) { // If the value can format itself directly into our buffer, do so. + + if (typeof(T).IsEnum) + { + if (Enum.TryFormatUnconstrained(value, _destination.Slice(_pos), out int charsWritten)) + { + _pos += charsWritten; + return true; + } + + return Fail(); + } + if (value is ISpanFormattable) { - int charsWritten; - if (((ISpanFormattable)value).TryFormat(_destination.Slice(_pos), out charsWritten, default, _provider)) // constrained call avoiding boxing for value types + if (((ISpanFormattable)value).TryFormat(_destination.Slice(_pos), out int charsWritten, default, _provider)) // constrained call avoiding boxing for value types { _pos += charsWritten; return true; @@ -3455,10 +3466,21 @@ public bool AppendFormatted(T value, string? format) if (value is IFormattable) { // If the value can format itself directly into our buffer, do so. + + if (typeof(T).IsEnum) + { + if (Enum.TryFormatUnconstrained(value, _destination.Slice(_pos), out int charsWritten, format)) + { + _pos += charsWritten; + return true; + } + + return Fail(); + } + if (value is ISpanFormattable) { - int charsWritten; - if (((ISpanFormattable)value).TryFormat(_destination.Slice(_pos), out charsWritten, format, _provider)) // constrained call avoiding boxing for value types + if (((ISpanFormattable)value).TryFormat(_destination.Slice(_pos), out int charsWritten, format, _provider)) // constrained call avoiding boxing for value types { _pos += charsWritten; return true; diff --git a/src/libraries/System.Private.CoreLib/src/System/Resources/NeutralResourcesLanguageAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Resources/NeutralResourcesLanguageAttribute.cs index ceed4e4133523..d912df64de133 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Resources/NeutralResourcesLanguageAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Resources/NeutralResourcesLanguageAttribute.cs @@ -21,7 +21,7 @@ public NeutralResourcesLanguageAttribute(string cultureName, UltimateResourceFal { ArgumentNullException.ThrowIfNull(cultureName); - if (!Enum.IsDefined(typeof(UltimateResourceFallbackLocation), location)) + if (!Enum.IsDefined(location)) throw new ArgumentException(SR.Format(SR.Arg_InvalidNeutralResourcesLanguage_FallbackLoc, location)); CultureName = cultureName; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs index 053528116e6ca..d045cf517a158 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/DefaultInterpolatedStringHandler.cs @@ -305,6 +305,19 @@ public void AppendFormatted(T value) if (value is IFormattable) { // If the value can format itself directly into our buffer, do so. + + if (typeof(T).IsEnum) + { + int charsWritten; + while (!Enum.TryFormatUnconstrained(value, _chars.Slice(_pos), out charsWritten)) + { + Grow(); + } + + _pos += charsWritten; + return; + } + if (value is ISpanFormattable) { int charsWritten; @@ -329,6 +342,7 @@ public void AppendFormatted(T value) AppendStringDirect(s); } } + /// Writes the specified value to the handler. /// The value to write. /// The format string. @@ -353,6 +367,19 @@ public void AppendFormatted(T value, string? format) if (value is IFormattable) { // If the value can format itself directly into our buffer, do so. + + if (typeof(T).IsEnum) + { + int charsWritten; + while (!Enum.TryFormatUnconstrained(value, _chars.Slice(_pos), out charsWritten, format)) + { + Grow(); + } + + _pos += charsWritten; + return; + } + if (value is ISpanFormattable) { int charsWritten; diff --git a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs index dcdc5659cb536..8fd7947e8797a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System { @@ -99,17 +100,21 @@ public override MemberInfo[] GetDefaultMembers() if (!(valueType.IsActualEnum || IsIntegerType(valueType))) throw new ArgumentException(SR.Arg_MustBeEnumBaseTypeOrEnum, nameof(value)); - ulong ulValue = Enum.ToUInt64(value); - - return Enum.GetEnumName(this, ulValue); + // Map the value to a ulong and then look up that value in the enum. + // This supports numerical values of different types than the enum + // or its underlying type. + return Enum.GetName(this, Enum.ToUInt64(value)); } + private static void ThrowMustBeEnum() => + throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); + public override string[] GetEnumNames() { if (!IsActualEnum) - throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); + ThrowMustBeEnum(); - string[] ret = Enum.InternalGetNames(this); + string[] ret = Enum.GetNamesNoCopy(this); // Make a copy since we can't hand out the same array since users can modify them return new ReadOnlySpan(ret).ToArray(); @@ -119,20 +124,21 @@ public override string[] GetEnumNames() public override Array GetEnumValues() { if (!IsActualEnum) - throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); + ThrowMustBeEnum(); - // Get all of the values - ulong[] values = Enum.InternalGetValues(this); - - // Create a generic Array + // Get all of the values as the underlying type and copy them to a new array of the enum type. + Array values = Enum.GetValuesAsUnderlyingTypeNoCopy(this); Array ret = Array.CreateInstance(this, values.Length); - +#if MONO + // TODO https://github.com/dotnet/runtime/issues/79224: + // Array.Copy can be used instead when bool[] is no longer supported, or if mono's Array.Copy is updated to support copying a bool[] to an EnumBackedByBool[]. for (int i = 0; i < values.Length; i++) { - object val = Enum.ToObject(this, values[i]); - ret.SetValue(val, i); + ret.SetValue(Enum.ToObject(this, values.GetValue(i)!), i); } - +#else + Array.Copy(values, ret, values.Length); +#endif return ret; } @@ -150,97 +156,15 @@ public override Array GetEnumValues() public override Array GetEnumValuesAsUnderlyingType() { if (!IsActualEnum) - throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); - - // Get all of the values - ulong[] values = Enum.InternalGetValues(this); - - switch (RuntimeTypeHandle.GetCorElementType(Enum.InternalGetUnderlyingType(this))) - { - - case CorElementType.ELEMENT_TYPE_U1: - { - var ret = new byte[values.Length]; - for (int i = 0; i < values.Length; i++) - { - ret[i] = (byte)values[i]; - } - return ret; - } - - case CorElementType.ELEMENT_TYPE_U2: - { - var ret = new ushort[values.Length]; - for (int i = 0; i < values.Length; i++) - { - ret[i] = (ushort)values[i]; - } - return ret; - } - - case CorElementType.ELEMENT_TYPE_U4: - { - var ret = new uint[values.Length]; - for (int i = 0; i < values.Length; i++) - { - ret[i] = (uint)values[i]; - } - return ret; - } - - case CorElementType.ELEMENT_TYPE_U8: - { - return (Array)values.Clone(); - } - - case CorElementType.ELEMENT_TYPE_I1: - { - var ret = new sbyte[values.Length]; - for (int i = 0; i < values.Length; i++) - { - ret[i] = (sbyte)values[i]; - } - return ret; - } - - case CorElementType.ELEMENT_TYPE_I2: - { - var ret = new short[values.Length]; - for (int i = 0; i < values.Length; i++) - { - ret[i] = (short)values[i]; - } - return ret; - } + ThrowMustBeEnum(); - case CorElementType.ELEMENT_TYPE_I4: - { - var ret = new int[values.Length]; - for (int i = 0; i < values.Length; i++) - { - ret[i] = (int)values[i]; - } - return ret; - } - - case CorElementType.ELEMENT_TYPE_I8: - { - var ret = new long[values.Length]; - for (int i = 0; i < values.Length; i++) - { - ret[i] = (long)values[i]; - } - return ret; - } - default: - throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType); - } + return Enum.GetValuesAsUnderlyingType(this); } public override Type GetEnumUnderlyingType() { if (!IsActualEnum) - throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); + ThrowMustBeEnum(); return Enum.InternalGetUnderlyingType(this); } @@ -283,44 +207,50 @@ public override bool IsEnumDefined(object value) ArgumentNullException.ThrowIfNull(value); if (!IsActualEnum) - throw new ArgumentException(SR.Arg_MustBeEnum, "enumType"); - - // Check if both of them are of the same type - RuntimeType valueType = (RuntimeType)value.GetType(); + ThrowMustBeEnum(); // If the value is an Enum then we need to extract the underlying value from it + RuntimeType valueType = (RuntimeType)value.GetType(); if (valueType.IsActualEnum) { + // The enum type must match this type. if (!valueType.IsEquivalentTo(this)) throw new ArgumentException(SR.Format(SR.Arg_EnumAndObjectMustBeSameType, valueType, this)); valueType = (RuntimeType)valueType.GetEnumUnderlyingType(); } - // If a string is passed in + // If a string is passed in, search the enum names with it. if (valueType == StringType) - { - // Get all of the Fields, calling GetHashEntry directly to avoid copying - string[] names = Enum.InternalGetNames(this); - return Array.IndexOf(names, value) >= 0; - } + return Array.IndexOf(Enum.GetNamesNoCopy(this), (string)value) >= 0; // If an enum or integer value is passed in - if (IsIntegerType(valueType)) - { - RuntimeType underlyingType = Enum.InternalGetUnderlyingType(this); - if (underlyingType != valueType) - throw new ArgumentException(SR.Format(SR.Arg_EnumUnderlyingTypeAndObjectMustBeSameType, valueType, underlyingType)); + if (!IsIntegerType(valueType)) + throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType); - ulong[] ulValues = Enum.InternalGetValues(this); - ulong ulValue = Enum.ToUInt64(value); + RuntimeType underlyingType = Enum.InternalGetUnderlyingType(this); + if (underlyingType != valueType) + throw new ArgumentException(SR.Format(SR.Arg_EnumUnderlyingTypeAndObjectMustBeSameType, valueType, underlyingType)); - return Array.BinarySearch(ulValues, ulValue) >= 0; - } - else + return GetTypeCode(underlyingType) switch { - throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType); - } + TypeCode.SByte => Enum.IsDefinedPrimitive(this, (sbyte)value), + TypeCode.Byte => Enum.IsDefinedPrimitive(this, (byte)value), + TypeCode.Int16 => Enum.IsDefinedPrimitive(this, (short)value), + TypeCode.UInt16 => Enum.IsDefinedPrimitive(this, (ushort)value), + TypeCode.Int32 => Enum.IsDefinedPrimitive(this, (int)value), + TypeCode.UInt32 => Enum.IsDefinedPrimitive(this, (uint)value), + TypeCode.Int64 => Enum.IsDefinedPrimitive(this, (long)value), + TypeCode.UInt64 => Enum.IsDefinedPrimitive(this, (ulong)value), + TypeCode.Single => Enum.IsDefinedPrimitive(this, (float)value), + TypeCode.Double => Enum.IsDefinedPrimitive(this, (double)value), + TypeCode.Char => Enum.IsDefinedPrimitive(this, (char)value), + TypeCode.Boolean => Enum.IsDefinedPrimitive(this, (bool)value ? (byte)1 : (byte)0), + _ => + underlyingType == typeof(nint) ? Enum.IsDefinedPrimitive(this, (nint)value) : + underlyingType == typeof(nuint) ? Enum.IsDefinedPrimitive(this, (nuint)value) : + throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType), + }; } protected override bool IsByRefImpl() => RuntimeTypeHandle.IsByRef(this); diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs index 65a228fa5fea4..2c9331c0e1d0b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/StringBuilder.cs @@ -1663,8 +1663,8 @@ internal StringBuilder AppendFormatHelper(IFormatProvider? provider, string form { // If arg is ISpanFormattable and the beginning doesn't need padding, // try formatting it into the remaining current chunk. - if (arg is ISpanFormattable spanFormattableArg && - (leftJustify || width == 0) && + if ((leftJustify || width == 0) && + arg is ISpanFormattable spanFormattableArg && spanFormattableArg.TryFormat(RemainingCurrentChunk, out int charsWritten, itemFormatSpan, provider)) { if ((uint)charsWritten > (uint)RemainingCurrentChunk.Length) @@ -2640,6 +2640,17 @@ public void AppendFormatted(T value) AppendFormattedWithTempSpace(value, 0, format: null); } } + else if (typeof(T).IsEnum) + { + if (Enum.TryFormatUnconstrained(value, _stringBuilder.RemainingCurrentChunk, out int charsWritten)) + { + _stringBuilder.m_ChunkLength += charsWritten; + } + else + { + AppendFormattedWithTempSpace(value, 0, format: null); + } + } else { _stringBuilder.Append(((IFormattable)value).ToString(format: null, _provider)); // constrained call avoiding boxing for value types @@ -2672,7 +2683,18 @@ public void AppendFormatted(T value, string? format) // if it only implements IFormattable, we come out even: only if it implements both do we // end up paying for an extra interface check. - if (value is ISpanFormattable) + if (typeof(T).IsEnum) + { + if (Enum.TryFormatUnconstrained(value, _stringBuilder.RemainingCurrentChunk, out int charsWritten, format)) + { + _stringBuilder.m_ChunkLength += charsWritten; + } + else + { + AppendFormattedWithTempSpace(value, 0, format); + } + } + else if (value is ISpanFormattable) { Span destination = _stringBuilder.RemainingCurrentChunk; if (((ISpanFormattable)value).TryFormat(destination, out int charsWritten, format, _provider)) // constrained call avoiding boxing for value types diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs index 5d45f01ca7633..b994e08acdc9f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/ValueStringBuilder.AppendFormat.cs @@ -211,8 +211,8 @@ internal void AppendFormatHelper(IFormatProvider? provider, string format, ReadO { // If arg is ISpanFormattable and the beginning doesn't need padding, // try formatting it into the remaining current chunk. - if (arg is ISpanFormattable spanFormattableArg && - (leftJustify || width == 0) && + if ((leftJustify || width == 0) && + arg is ISpanFormattable spanFormattableArg && spanFormattableArg.TryFormat(_chars.Slice(_pos), out int charsWritten, itemFormatSpan, provider)) { _pos += charsWritten; diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 1f0e03112c8f2..55dda55d8e5bf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -692,7 +692,7 @@ internal static void ThrowForUnsupportedIntrinsicsVector256BaseType() [MethodImpl(MethodImplOptions.NoInlining)] private static string GetArgumentName(ExceptionArgument argument) { - Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument), + Debug.Assert(Enum.IsDefined(argument), "The enum value is not defined, please check the ExceptionArgument Enum."); return argument.ToString(); @@ -912,7 +912,7 @@ private static string GetArgumentName(ExceptionArgument argument) [MethodImpl(MethodImplOptions.NoInlining)] private static string GetResourceString(ExceptionResource resource) { - Debug.Assert(Enum.IsDefined(typeof(ExceptionResource), resource), + Debug.Assert(Enum.IsDefined(resource), "The enum value is not defined, please check the ExceptionResource Enum."); return SR.GetResourceString(resource.ToString()); diff --git a/src/libraries/System.Private.Uri/src/System/UriExt.cs b/src/libraries/System.Private.Uri/src/System/UriExt.cs index 4865dd3fc82b2..d38ac6e1a8775 100644 --- a/src/libraries/System.Private.Uri/src/System/UriExt.cs +++ b/src/libraries/System.Private.Uri/src/System/UriExt.cs @@ -17,8 +17,6 @@ private void CreateThis(string? uri, bool dontEscape, UriKind uriKind, in UriCre { DebugAssertInCtor(); - // if (!Enum.IsDefined(typeof(UriKind), uriKind)) -- We currently believe that Enum.IsDefined() is too slow - // to be used here. if ((int)uriKind < (int)UriKind.RelativeOrAbsolute || (int)uriKind > (int)UriKind.Relative) { throw new ArgumentException(SR.Format(SR.net_uri_InvalidUriKind, uriKind)); @@ -622,8 +620,6 @@ private Uri(Flags flags, UriParser? uriParser, string uri) // internal static Uri? CreateHelper(string uriString, bool dontEscape, UriKind uriKind, ref UriFormatException? e, in UriCreationOptions creationOptions = default) { - // if (!Enum.IsDefined(typeof(UriKind), uriKind)) -- We currently believe that Enum.IsDefined() is too slow - // to be used here. if ((int)uriKind < (int)UriKind.RelativeOrAbsolute || (int)uriKind > (int)UriKind.Relative) { throw new ArgumentException(SR.Format(SR.net_uri_InvalidUriKind, uriKind)); diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/OptimizerPatterns.cs b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/OptimizerPatterns.cs index c02d1c707b160..ef53d2f1e6db9 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/OptimizerPatterns.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Xsl/IlGen/OptimizerPatterns.cs @@ -242,7 +242,7 @@ public object GetArgument(OptimizerPatternArgument argNum) /// public void AddPattern(OptimizerPatternName pattern) { - Debug.Assert(Enum.IsDefined(typeof(OptimizerPatternName), pattern)); + Debug.Assert(Enum.IsDefined(pattern)); Debug.Assert((int)pattern < 32); Debug.Assert(!_isReadOnly, "This OptimizerPatterns instance is read-only."); _patterns |= (1 << (int)pattern); @@ -253,7 +253,7 @@ public void AddPattern(OptimizerPatternName pattern) /// public bool MatchesPattern(OptimizerPatternName pattern) { - Debug.Assert(Enum.IsDefined(typeof(OptimizerPatternName), pattern)); + Debug.Assert(Enum.IsDefined(pattern)); return (_patterns & (1 << (int)pattern)) != 0; } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 1eeac52d4ecdb..50c7708cdfc74 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2311,7 +2311,7 @@ protected EntryPointNotFoundException(System.Runtime.Serialization.Serialization public EntryPointNotFoundException(string? message) { } public EntryPointNotFoundException(string? message, System.Exception? inner) { } } - public abstract partial class Enum : System.ValueType, System.IComparable, System.IConvertible, System.IFormattable + public abstract partial class Enum : System.ValueType, System.IComparable, System.IConvertible, System.ISpanFormattable { protected Enum() { } public int CompareTo(object? target) { throw null; } @@ -2374,6 +2374,8 @@ protected Enum() { } public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("EnumFormat")] string? format) { throw null; } [System.ObsoleteAttribute("The provider argument is not used. Use ToString(String) instead.")] public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("EnumFormat")] string? format, System.IFormatProvider? provider) { throw null; } + public static bool TryFormat(TEnum value, System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("EnumFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan)) where TEnum : struct { throw null; } + bool ISpanFormattable.TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } public static bool TryParse(System.Type enumType, System.ReadOnlySpan value, bool ignoreCase, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out object? result) { throw null; } public static bool TryParse(System.Type enumType, System.ReadOnlySpan value, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out object? result) { throw null; } public static bool TryParse(System.Type enumType, string? value, bool ignoreCase, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out object? result) { throw null; } @@ -4319,7 +4321,6 @@ public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, S public static bool IsAndroid() { throw null; } public static bool IsAndroidVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0) { throw null; } public static bool IsBrowser() { throw null; } - // TODO public static bool IsWasi() { throw null; } see https://github.com/dotnet/runtime/issues/78389 public static bool IsFreeBSD() { throw null; } public static bool IsFreeBSDVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0) { throw null; } [System.Runtime.Versioning.SupportedOSPlatformGuardAttribute("maccatalyst")] diff --git a/src/libraries/System.Runtime/tests/System/EnumTests.cs b/src/libraries/System.Runtime/tests/System/EnumTests.cs index a4cbc4a52ac17..5c0928a5900d9 100644 --- a/src/libraries/System.Runtime/tests/System/EnumTests.cs +++ b/src/libraries/System.Runtime/tests/System/EnumTests.cs @@ -88,16 +88,17 @@ public static IEnumerable Parse_TestData() yield return new object[] { "Value1", false, Enum.ToObject(s_boolEnumType, true) }; yield return new object[] { "vaLue2", true, Enum.ToObject(s_boolEnumType, false) }; - if (!PlatformDetection.IsMonoRuntime) // [ActiveIssue("https://github.com/dotnet/runtime/issues/29266")] - { - // Single - parses successfully, but doesn't properly represent the underlying value - yield return new object[] { "Value1", false, Enum.GetValues(s_floatEnumType).GetValue(0) }; - yield return new object[] { "vaLue2", true, Enum.GetValues(s_floatEnumType).GetValue(0) }; + // Single + yield return new object[] { "Value1", false, Enum.GetValues(s_floatEnumType).GetValue(1) }; + yield return new object[] { "vaLue2", true, Enum.GetValues(s_floatEnumType).GetValue(2) }; + yield return new object[] { "1", false, Enum.GetValues(s_floatEnumType).GetValue(1) }; + yield return new object[] { "1.0", false, Enum.GetValues(s_floatEnumType).GetValue(1) }; - // Double - parses successfully, but doesn't properly represent the underlying value - yield return new object[] { "Value1", false, Enum.GetValues(s_doubleEnumType).GetValue(0) }; - yield return new object[] { "vaLue2", true, Enum.GetValues(s_doubleEnumType).GetValue(0) }; - } + // Double + yield return new object[] { "Value1", false, Enum.GetValues(s_doubleEnumType).GetValue(1) }; + yield return new object[] { "vaLue2", true, Enum.GetValues(s_doubleEnumType).GetValue(2) }; + yield return new object[] { "1", false, Enum.GetValues(s_doubleEnumType).GetValue(1) }; + yield return new object[] { "1.0", false, Enum.GetValues(s_doubleEnumType).GetValue(1) }; } // SimpleEnum @@ -213,16 +214,6 @@ public static IEnumerable Parse_Invalid_TestData() yield return new object[] { s_boolEnumType, bool.TrueString, false, typeof(ArgumentException) }; yield return new object[] { s_boolEnumType, bool.FalseString, false, typeof(ArgumentException) }; - // Single - yield return new object[] { s_floatEnumType, "1", false, typeof(ArgumentException) }; - yield return new object[] { s_floatEnumType, "5", false, typeof(ArgumentException) }; - yield return new object[] { s_floatEnumType, "1.0", false, typeof(ArgumentException) }; - - // Double - yield return new object[] { s_doubleEnumType, "1", false, typeof(ArgumentException) }; - yield return new object[] { s_doubleEnumType, "5", false, typeof(ArgumentException) }; - yield return new object[] { s_doubleEnumType, "1.0", false, typeof(ArgumentException) }; - // IntPtr yield return new object[] { s_intPtrEnumType, "1", false, typeof(InvalidCastException) }; yield return new object[] { s_intPtrEnumType, "5", false, typeof(InvalidCastException) }; @@ -523,7 +514,7 @@ public void GetName_NullEnumType_ThrowsArgumentNullException() [InlineData(typeof(Enum))] public void GetName_EnumTypeNotEnum_ThrowsArgumentException(Type enumType) { - AssertExtensions.Throws(null, () => Enum.GetName(enumType, 1)); + AssertExtensions.Throws("enumType", () => Enum.GetName(enumType, 1)); } [Fact] @@ -980,6 +971,14 @@ public static IEnumerable ToObject_TestData() // Bool yield return new object[] { s_boolEnumType, true, Enum.Parse(s_boolEnumType, "Value1") }; yield return new object[] { s_boolEnumType, false, Enum.Parse(s_boolEnumType, "Value2") }; + + // Float + yield return new object[] { s_floatEnumType, 1.0f, Enum.Parse(s_floatEnumType, "Value1") }; + yield return new object[] { s_floatEnumType, 2.0f, Enum.Parse(s_floatEnumType, "Value2") }; + + // Double + yield return new object[] { s_doubleEnumType, 1.0, Enum.Parse(s_doubleEnumType, "Value1") }; + yield return new object[] { s_doubleEnumType, 2.0, Enum.Parse(s_doubleEnumType, "Value2") }; } } @@ -1023,8 +1022,6 @@ public static IEnumerable ToObject_InvalidValue_TestData() if (PlatformDetection.IsReflectionEmitSupported) { - yield return new object[] { s_floatEnumType, 1.0f, typeof(ArgumentException) }; - yield return new object[] { s_doubleEnumType, 1.0, typeof(ArgumentException) }; yield return new object[] { s_intPtrEnumType, (IntPtr)1, typeof(ArgumentException) }; yield return new object[] { s_uintPtrEnumType, (UIntPtr)1, typeof(ArgumentException) }; } @@ -1321,7 +1318,7 @@ public void GetNames_InvokeSimpleEnum_ReturnsExpected() [Fact] public void GetNames_InvokeSByteEnum_ReturnsExpected() { - var expected = new string[] { "One", "Two", "Max", "Min" }; + var expected = new string[] { "Min", "One", "Two", "Max" }; Assert.Equal(expected, Enum.GetNames(typeof(SByteEnum))); Assert.NotSame(Enum.GetNames(typeof(SByteEnum)), Enum.GetNames(typeof(SByteEnum))); Assert.Equal(expected, Enum.GetNames()); @@ -1339,7 +1336,7 @@ public void GetNames_InvokeByteEnum_ReturnsExpected() [Fact] public void GetNames_InvokeInt16Enum_ReturnsExpected() { - var expected = new string[] { "One", "Two", "Max", "Min" }; + var expected = new string[] { "Min", "One", "Two", "Max" }; Assert.Equal(expected, Enum.GetNames(typeof(Int16Enum))); Assert.NotSame(Enum.GetNames(typeof(Int16Enum)), Enum.GetNames(typeof(Int16Enum))); Assert.Equal(expected, Enum.GetNames()); @@ -1357,7 +1354,7 @@ public void GetNames_InvokeUInt16Enum_ReturnsExpected() [Fact] public void GetNames_InvokeInt32Enum_ReturnsExpected() { - var expected = new string[] { "One", "Two", "Max", "Min" }; + var expected = new string[] { "Min", "One", "Two", "Max" }; Assert.Equal(expected, Enum.GetNames(typeof(Int32Enum))); Assert.NotSame(Enum.GetNames(typeof(Int32Enum)), Enum.GetNames(typeof(Int32Enum))); Assert.Equal(expected, Enum.GetNames()); @@ -1375,7 +1372,7 @@ public void GetNames_InvokeUInt32Enum_ReturnsExpected() [Fact] public void GetNames_InvokeInt64Enum_ReturnsExpected() { - var expected = new string[] { "One", "Two", "Max", "Min" }; + var expected = new string[] { "Min", "One", "Two", "Max" }; Assert.Equal(expected, Enum.GetNames(typeof(Int64Enum))); Assert.NotSame(Enum.GetNames(typeof(Int64Enum)), Enum.GetNames(typeof(Int64Enum))); Assert.Equal(expected, Enum.GetNames()); @@ -1409,7 +1406,7 @@ public void GetNames_InvokeBoolEnum_ReturnsExpected() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] public void GetNames_InvokeSingleEnum_ReturnsExpected() { - var expected = new string[] { "Value1", "Value2", "Value0x3f06", "Value0x3000", "Value0x0f06", "Value0x1000", "Value0x0000", "Value0x0010", "Value0x3f16" }; + var expected = new string[] { "Value0x0000", "Value1", "Value2", "Value0x0010", "Value0x0f06", "Value0x1000", "Value0x3000", "Value0x3f06", "Value0x3f16" }; Assert.Equal(expected, Enum.GetNames(s_floatEnumType)); Assert.NotSame(Enum.GetNames(s_floatEnumType), Enum.GetNames(s_floatEnumType)); } @@ -1417,7 +1414,7 @@ public void GetNames_InvokeSingleEnum_ReturnsExpected() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] public void GetNames_InvokeDoubleEnum_ReturnsExpected() { - var expected = new string[] { "Value1", "Value2", "Value0x3f06", "Value0x3000", "Value0x0f06", "Value0x1000", "Value0x0000", "Value0x0010", "Value0x3f16" }; + var expected = new string[] { "Value0x0000", "Value1", "Value2", "Value0x0010", "Value0x0f06", "Value0x1000", "Value0x3000", "Value0x3f06", "Value0x3f16" }; Assert.Equal(expected, Enum.GetNames(s_doubleEnumType)); Assert.NotSame(Enum.GetNames(s_doubleEnumType), Enum.GetNames(s_doubleEnumType)); } @@ -1466,7 +1463,7 @@ public void GetValues_InvokeSimpleEnumEnum_ReturnsExpected() [Fact] public void GetValues_InvokeSByteEnum_ReturnsExpected() { - var expected = new SByteEnum[] { SByteEnum.One, SByteEnum.Two, SByteEnum.Max, SByteEnum.Min }; + var expected = new SByteEnum[] { SByteEnum.Min, SByteEnum.One, SByteEnum.Two, SByteEnum.Max }; Assert.Equal(expected, Enum.GetValues(typeof(SByteEnum))); Assert.NotSame(Enum.GetValues(typeof(SByteEnum)), Enum.GetValues(typeof(SByteEnum))); Assert.Equal(expected, Enum.GetValues()); @@ -1484,7 +1481,7 @@ public void GetValues_InvokeByteEnum_ReturnsExpected() [Fact] public void GetValues_InvokeInt16Enum_ReturnsExpected() { - var expected = new Int16Enum[] { Int16Enum.One, Int16Enum.Two, Int16Enum.Max, Int16Enum.Min }; + var expected = new Int16Enum[] { Int16Enum.Min, Int16Enum.One, Int16Enum.Two, Int16Enum.Max }; Assert.Equal(expected, Enum.GetValues(typeof(Int16Enum))); Assert.NotSame(Enum.GetValues(typeof(Int16Enum)), Enum.GetValues(typeof(Int16Enum))); Assert.Equal(expected, Enum.GetValues()); @@ -1502,7 +1499,7 @@ public void GetValues_InvokeUInt16Enum_ReturnsExpected() [Fact] public void GetValues_InvokeInt32Enum_ReturnsExpected() { - var expected = new Int32Enum[] { Int32Enum.One, Int32Enum.Two, Int32Enum.Max, Int32Enum.Min }; + var expected = new Int32Enum[] { Int32Enum.Min, Int32Enum.One, Int32Enum.Two, Int32Enum.Max }; Assert.Equal(expected, Enum.GetValues(typeof(Int32Enum))); Assert.NotSame(Enum.GetValues(typeof(Int32Enum)), Enum.GetValues(typeof(Int32Enum))); Assert.Equal(expected, Enum.GetValues()); @@ -1520,7 +1517,7 @@ public void GetValues_InvokeUInt32Enum_ReturnsExpected() [Fact] public void GetValues_InvokeInt64Enum_ReturnsExpected() { - var expected = new Int64Enum[] { Int64Enum.One, Int64Enum.Two, Int64Enum.Max, Int64Enum.Min }; + var expected = new Int64Enum[] { Int64Enum.Min, Int64Enum.One, Int64Enum.Two, Int64Enum.Max }; Assert.Equal(expected, Enum.GetValues(typeof(Int64Enum))); Assert.NotSame(Enum.GetValues(typeof(Int64Enum)), Enum.GetValues(typeof(Int64Enum))); Assert.Equal(expected, Enum.GetValues()); @@ -1554,7 +1551,7 @@ public void GetValues_InvokeBoolEnum_ReturnsExpected() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] public void GetValues_InvokeSingleEnum_ReturnsExpected() { - var expected = new object[] { Enum.Parse(s_floatEnumType, "Value1"), Enum.Parse(s_floatEnumType, "Value2"), Enum.Parse(s_floatEnumType, "Value0x3f06"), Enum.Parse(s_floatEnumType, "Value0x3000"), Enum.Parse(s_floatEnumType, "Value0x0f06"), Enum.Parse(s_floatEnumType, "Value0x1000"), Enum.Parse(s_floatEnumType, "Value0x0000"), Enum.Parse(s_floatEnumType, "Value0x0010"), Enum.Parse(s_floatEnumType, "Value0x3f16") }; + var expected = new object[] { Enum.Parse(s_floatEnumType, "Value0x0000"), Enum.Parse(s_floatEnumType, "Value1"), Enum.Parse(s_floatEnumType, "Value2"), Enum.Parse(s_floatEnumType, "Value0x0010"), Enum.Parse(s_floatEnumType, "Value0x0f06"), Enum.Parse(s_floatEnumType, "Value0x1000"), Enum.Parse(s_floatEnumType, "Value0x3000"), Enum.Parse(s_floatEnumType, "Value0x3f06"), Enum.Parse(s_floatEnumType, "Value0x3f16") }; Assert.Equal(expected, Enum.GetValues(s_floatEnumType)); Assert.NotSame(Enum.GetValues(s_floatEnumType), Enum.GetValues(s_floatEnumType)); } @@ -1562,7 +1559,7 @@ public void GetValues_InvokeSingleEnum_ReturnsExpected() [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] public void GetValues_InvokeDoubleEnum_ReturnsExpected() { - var expected = new object[] { Enum.Parse(s_doubleEnumType, "Value1"), Enum.Parse(s_doubleEnumType, "Value2"), Enum.Parse(s_doubleEnumType, "Value0x3f06"), Enum.Parse(s_doubleEnumType, "Value0x3000"), Enum.Parse(s_doubleEnumType, "Value0x0f06"), Enum.Parse(s_doubleEnumType, "Value0x1000"), Enum.Parse(s_doubleEnumType, "Value0x0000"), Enum.Parse(s_doubleEnumType, "Value0x0010"), Enum.Parse(s_doubleEnumType, "Value0x3f16") }; + var expected = new object[] { Enum.Parse(s_doubleEnumType, "Value0x0000"), Enum.Parse(s_doubleEnumType, "Value1"), Enum.Parse(s_doubleEnumType, "Value2"), Enum.Parse(s_doubleEnumType, "Value0x0010"), Enum.Parse(s_doubleEnumType, "Value0x0f06"), Enum.Parse(s_doubleEnumType, "Value0x1000"), Enum.Parse(s_doubleEnumType, "Value0x3000"), Enum.Parse(s_doubleEnumType, "Value0x3f06"), Enum.Parse(s_doubleEnumType, "Value0x3f16") }; Assert.Equal(expected, Enum.GetValues(s_doubleEnumType)); Assert.NotSame(Enum.GetValues(s_doubleEnumType), Enum.GetValues(s_doubleEnumType)); } @@ -1592,7 +1589,7 @@ public static void GetValues_NullEnumType_ThrowsArgumentNullException() [Fact] public void GetValuesAsUnderlyingType_InvokeSByteEnum_ReturnsExpected() { - Array expected = new sbyte[] { 1, 2, sbyte.MaxValue, sbyte.MinValue }; + Array expected = new sbyte[] { sbyte.MinValue, 1, 2, sbyte.MaxValue }; Assert.Equal(expected, Enum.GetValuesAsUnderlyingType(typeof(SByteEnum))); Assert.Equal(expected, Enum.GetValuesAsUnderlyingType()); } @@ -1608,7 +1605,7 @@ public void GetValuesAsUnderlyingType_InvokeByteEnum_ReturnsExpected() [Fact] public void GetValuesAsUnderlyingType_InvokeInt16Enum_ReturnsExpected() { - Array expected = new short[] { 1, 2, short.MaxValue, short.MinValue }; + Array expected = new short[] { short.MinValue, 1, 2, short.MaxValue }; Assert.Equal(expected, Enum.GetValuesAsUnderlyingType(typeof(Int16Enum))); Assert.Equal(expected, Enum.GetValuesAsUnderlyingType()); } @@ -1624,7 +1621,7 @@ public void GetValuesAsUnderlyingType_InvokeUInt16Enum_ReturnsExpected() [Fact] public void GetValuesAsUnderlyingType_InvokeInt32Enum_ReturnsExpected() { - Array expected = new int[] { 1, 2, int.MaxValue, int.MinValue }; + Array expected = new int[] { int.MinValue, 1, 2, int.MaxValue }; Assert.Equal(expected, Enum.GetValuesAsUnderlyingType(typeof(Int32Enum))); Assert.Equal(expected, Enum.GetValuesAsUnderlyingType()); } @@ -1640,7 +1637,7 @@ public void GetValuesAsUnderlyingType_InvokeUInt32Enum_ReturnsExpected() [Fact] public void GetValuesAsUnderlyingType_InvokeInt64Enum_ReturnsExpected() { - Array expected = new long[] { 1, 2, long.MaxValue, long.MinValue }; + Array expected = new long[] { long.MinValue, 1, 2, long.MaxValue }; Assert.Equal(expected, Enum.GetValuesAsUnderlyingType(typeof(Int64Enum))); Assert.Equal(expected, Enum.GetValuesAsUnderlyingType()); } @@ -1986,6 +1983,252 @@ public static IEnumerable ToString_Format_TestData() yield return new object[] { AttributeTargets.Class | AttributeTargets.Delegate, "G", "Class, Delegate" }; } + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public static void ToString_TryFormat(bool validateDestinationSpanSizeCheck, bool validateExtraSpanSpaceNotFilled) + { + // Format "D": the decimal equivalent of the value is returned. + // Format "X": value in hex form without a leading "0x" + // Format "F": value is treated as a bit field that contains one or more flags that consist of one or more bits. + // If value is equal to a combination of named enumerated constants, a delimiter-separated list of the names + // of those constants is returned. value is searched for flags, going from the flag with the largest value + // to the smallest value. For each flag that corresponds to a bit field in value, the name of the constant + // is concatenated to the delimiter-separated list. The value of that flag is then excluded from further + // consideration, and the search continues for the next flag. + // If value is not equal to a combination of named enumerated constants, the decimal equivalent of value is returned. + // Format "G": if value is equal to a named enumerated constant, the name of that constant is returned. + // Otherwise, if "[Flags]" present, do as Format "F" - else return the decimal value of "value". + + // "D": SByte + TryFormat(SByteEnum.Min, "D", "-128", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(SByteEnum.One, "D", "1", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(SByteEnum.Two, "D", "2", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((SByteEnum)99, "D", "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(SByteEnum.Max, "D", "127", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "D": Byte + TryFormat(ByteEnum.Min, "D", "0", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(ByteEnum.One, "D", "1", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(ByteEnum.Two, "D", "2", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((ByteEnum)99, "D", "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(ByteEnum.Max, "D", "255", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "D": Int16 + TryFormat(Int16Enum.Min, "D", "-32768", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int16Enum.One, "D", "1", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int16Enum.Two, "D", "2", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int16Enum)99, "D", "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int16Enum.Max, "D", "32767", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "D": UInt16 + TryFormat(UInt16Enum.Min, "D", "0", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt16Enum.One, "D", "1", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt16Enum.Two, "D", "2", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt16Enum)99, "D", "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt16Enum.Max, "D", "65535", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "D": Int32 + TryFormat(Int32Enum.Min, "D", "-2147483648", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int32Enum.One, "D", "1", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int32Enum.Two, "D", "2", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int32Enum)99, "D", "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int32Enum.Max, "D", "2147483647", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "D": UInt32 + TryFormat(UInt32Enum.Min, "D", "0", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt32Enum.One, "D", "1", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt32Enum.Two, "D", "2", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt32Enum)99, "D", "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt32Enum.Max, "D", "4294967295", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "D": Int64 + TryFormat(Int64Enum.Min, "D", "-9223372036854775808", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int64Enum.One, "D", "1", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int64Enum.Two, "D", "2", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int64Enum)99, "D", "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int64Enum.Max, "D", "9223372036854775807", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "D": UInt64 + TryFormat(UInt64Enum.Min, "D", "0", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt64Enum.One, "D", "1", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt64Enum.Two, "D", "2", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt64Enum)99, "D", "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt64Enum.Max, "D", "18446744073709551615", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "D": SimpleEnum + TryFormat(SimpleEnum.Red, "D", "1", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "X": SByte + TryFormat(SByteEnum.Min, "X", "80", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(SByteEnum.One, "X", "01", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(SByteEnum.Two, "X", "02", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((SByteEnum)99, "X", "63", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(SByteEnum.Max, "X", "7F", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "X": Byte + TryFormat(ByteEnum.Min, "X", "00", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(ByteEnum.One, "X", "01", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(ByteEnum.Two, "X", "02", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((ByteEnum)99, "X", "63", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(ByteEnum.Max, "X", "FF", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "X": Int16 + TryFormat(Int16Enum.Min, "X", "8000", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int16Enum.One, "X", "0001", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int16Enum.Two, "X", "0002", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int16Enum)99, "X", "0063", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int16Enum.Max, "X", "7FFF", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "X": UInt16 + TryFormat(UInt16Enum.Min, "X", "0000", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt16Enum.One, "X", "0001", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt16Enum.Two, "X", "0002", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt16Enum)99, "X", "0063", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt16Enum.Max, "X", "FFFF", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "X": UInt32 + TryFormat(UInt32Enum.Min, "X", "00000000", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt32Enum.One, "X", "00000001", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt32Enum.Two, "X", "00000002", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt32Enum)99, "X", "00000063", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt32Enum.Max, "X", "FFFFFFFF", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "X": Int32 + TryFormat(Int32Enum.Min, "X", "80000000", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int32Enum.One, "X", "00000001", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int32Enum.Two, "X", "00000002", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int32Enum)99, "X", "00000063", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int32Enum.Max, "X", "7FFFFFFF", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "X:" Int64 + TryFormat(Int64Enum.Min, "X", "8000000000000000", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int64Enum.One, "X", "0000000000000001", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int64Enum.Two, "X", "0000000000000002", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int64Enum)99, "X", "0000000000000063", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int64Enum.Max, "X", "7FFFFFFFFFFFFFFF", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "X": UInt64 + TryFormat(UInt64Enum.Min, "X", "0000000000000000", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt64Enum.One, "X", "0000000000000001", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt64Enum.Two, "X", "0000000000000002", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt64Enum)99, "X", "0000000000000063", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt64Enum.Max, "X", "FFFFFFFFFFFFFFFF", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "X": SimpleEnum + TryFormat(SimpleEnum.Red, "X", "00000001", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "F": SByte + TryFormat(SByteEnum.Min, "F", "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(SByteEnum.One | SByteEnum.Two, "F", "One, Two", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((SByteEnum)5, "F", "5", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(SByteEnum.Max, "F", "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "F": Byte + TryFormat(ByteEnum.Min, "F", "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(ByteEnum.One | ByteEnum.Two, "F", "One, Two", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((ByteEnum)5, "F", "5", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(ByteEnum.Max, "F", "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "F": Int16 + TryFormat(Int16Enum.Min, "F", "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int16Enum.One | Int16Enum.Two, "F", "One, Two", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int16Enum)5, "F", "5", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int16Enum.Max, "F", "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "F": UInt16 + TryFormat(UInt16Enum.Min, "F", "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt16Enum.One | UInt16Enum.Two, "F", "One, Two", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt16Enum)5, "F", "5", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt16Enum.Max, "F", "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "F": Int32 + TryFormat(Int32Enum.Min, "F", "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int32Enum.One | Int32Enum.Two, "F", "One, Two", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int32Enum)5, "F", "5", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int32Enum.Max, "F", "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "F": UInt32 + TryFormat(UInt32Enum.Min, "F", "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt32Enum.One | UInt32Enum.Two, "F", "One, Two", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt32Enum)5, "F", "5", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt32Enum.Max, "F", "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "F": Int64 + TryFormat(Int64Enum.Min, "F", "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int64Enum.One | Int64Enum.Two, "F", "One, Two", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int64Enum)5, "F", "5", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(Int64Enum.Max, "F", "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "F": UInt64 + TryFormat(UInt64Enum.Min, "F", "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt64Enum.One | UInt64Enum.Two, "F", "One, Two", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt64Enum)5, "F", "5", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(UInt64Enum.Max, "F", "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "F": SimpleEnum + TryFormat(SimpleEnum.Red, "F", "Red", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat(SimpleEnum.Blue, "F", "Blue", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((SimpleEnum)99, "F", "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((SimpleEnum)0, "F", "0", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // Not found + + // "F": Flags Attribute + TryFormat(AttributeTargets.Class | AttributeTargets.Delegate, "F", "Class, Delegate", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + foreach (string defaultFormatSpecifier in new[] { "G", null, "" }) + { + // "G": SByte + TryFormat(SByteEnum.Min, defaultFormatSpecifier, "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((SByteEnum)3, defaultFormatSpecifier, "3", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // No [Flags] attribute + TryFormat(SByteEnum.Max, defaultFormatSpecifier, "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "G": Byte + TryFormat(ByteEnum.Min, defaultFormatSpecifier, "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((ByteEnum)0xff, defaultFormatSpecifier, "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((ByteEnum)3, defaultFormatSpecifier, "3", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // No [Flags] attribute + TryFormat(ByteEnum.Max, defaultFormatSpecifier, "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "G": Int16 + TryFormat(Int16Enum.Min, defaultFormatSpecifier, "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int16Enum)3, defaultFormatSpecifier, "3", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // No [Flags] attribute + TryFormat(Int16Enum.Max, defaultFormatSpecifier, "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "G": UInt16 + TryFormat(UInt16Enum.Min, defaultFormatSpecifier, "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt16Enum)3, defaultFormatSpecifier, "3", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // No [Flags] attribute + TryFormat(UInt16Enum.Max, defaultFormatSpecifier, "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "G": Int32 + TryFormat(Int32Enum.Min, defaultFormatSpecifier, "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int32Enum)3, defaultFormatSpecifier, "3", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // No [Flags] attribute + TryFormat(Int32Enum.Max, defaultFormatSpecifier, "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "G": UInt32 + TryFormat(UInt32Enum.Min, defaultFormatSpecifier, "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt32Enum)3, defaultFormatSpecifier, "3", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // No [Flags] attribute + TryFormat(UInt32Enum.Max, defaultFormatSpecifier, "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "G": Int64 + TryFormat(Int64Enum.Min, defaultFormatSpecifier, "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((Int64Enum)3, defaultFormatSpecifier, "3", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // No [Flags] attribute + TryFormat(Int64Enum.Max, defaultFormatSpecifier, "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "G": UInt64 + TryFormat(UInt64Enum.Min, defaultFormatSpecifier, "Min", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((UInt64Enum)3, defaultFormatSpecifier, "3", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // No [Flags] attribute + TryFormat(UInt64Enum.Max, defaultFormatSpecifier, "Max", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + + // "G": SimpleEnum + TryFormat((SimpleEnum)99, defaultFormatSpecifier, "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((SimpleEnum)99, defaultFormatSpecifier, "99", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + TryFormat((SimpleEnum)0, defaultFormatSpecifier, "0", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); // Not found + + // "G": Flags Attribute + TryFormat(AttributeTargets.Class | AttributeTargets.Delegate, defaultFormatSpecifier, "Class, Delegate", validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + } + } + #pragma warning disable 618 // ToString with IFormatProvider is marked as Obsolete. [Theory] [MemberData(nameof(ToString_Format_TestData))] @@ -2044,6 +2287,15 @@ public static IEnumerable Format_TestData() // Format: G with Flags Attribute yield return new object[] { typeof(AttributeTargets), (int)(AttributeTargets.Class | AttributeTargets.Delegate), "G", "Class, Delegate" }; + + // nint/nuint types + if (PlatformDetection.IsReflectionEmitSupported) + { + yield return new object[] { s_intPtrEnumType, (nint)1, "G", "1" }; + yield return new object[] { s_uintPtrEnumType, (nuint)2, "F", "2" }; + yield return new object[] { s_floatEnumType, 1.4f, "G", (1.4f).ToString() }; + yield return new object[] { s_doubleEnumType, 2.5, "F", (2.5).ToString() }; + } } [Theory] @@ -2055,6 +2307,90 @@ public static void Format(Type enumType, object value, string format, string exp Assert.Equal(expected, Enum.Format(enumType, value, format.ToLowerInvariant())); } + // Select test here, to avoid rewriting input params, as MemberData will does work for generic methods + private static void TryFormat(TEnum value, ReadOnlySpan format, string expected, bool validateDestinationSpanSizeCheck, bool validateExtraSpanSpaceNotFilled) where TEnum : struct, Enum + { + if (validateDestinationSpanSizeCheck) + { + TryFormat_WithDestinationSpanSizeTooSmall_ReturnsFalseWithNoCharsWritten(value, format, expected); + } + if (validateExtraSpanSpaceNotFilled) + { + TryFormat_WithDestinationSpanLargerThanExpected_ReturnsTrueWithExpectedAndExtraSpaceNotFilled(value, format, expected); + } + else + { + TryFormat_WithValidParameters_ReturnsTrueWithExpected(value, format, expected); + } + + if (format.Length == 1 && char.IsAsciiLetterUpper(format[0])) + { + TryFormat(value, (ReadOnlySpan)new char[1] { char.ToLowerInvariant(format[0]) }, expected, validateDestinationSpanSizeCheck, validateExtraSpanSpaceNotFilled); + } + } + + private static void TryFormat_WithValidParameters_ReturnsTrueWithExpected(TEnum value, ReadOnlySpan format, string expected) where TEnum : struct, Enum + { + Span destination = new char[expected.Length]; + + Assert.True(Enum.TryFormat(value, destination, out int charsWritten, format)); + Assert.Equal(expected, destination.ToString()); + Assert.Equal(expected.Length, charsWritten); + } + + private static void TryFormat_WithDestinationSpanSizeTooSmall_ReturnsFalseWithNoCharsWritten(TEnum value, ReadOnlySpan format, string expected) where TEnum : struct, Enum + { + int oneLessThanExpectedLength = expected.Length - 1; + Span destination = new char[oneLessThanExpectedLength]; + + Assert.False(Enum.TryFormat(value, destination, out int charsWritten, format)); + Assert.Equal(new string('\0', oneLessThanExpectedLength), destination.ToString()); + Assert.Equal(0, charsWritten); + } + + private static void TryFormat_WithDestinationSpanLargerThanExpected_ReturnsTrueWithExpectedAndExtraSpaceNotFilled(TEnum value, ReadOnlySpan format, string expected) where TEnum : struct, Enum + { + int oneMoreThanExpectedLength = expected.Length + 1; + Span destination = new char[oneMoreThanExpectedLength]; + + Assert.True(Enum.TryFormat(value, destination, out int charsWritten, format)); + Assert.Equal(expected + '\0', destination.ToString()); + Assert.Equal(expected.Length, charsWritten); + } + + [Fact] + private static void TryFormat_WithEmptySpan_ReturnsFalseWithNoCharsWritten() + { + Span destination = new char[0]; + + Assert.False(Enum.TryFormat(SimpleEnum.Green, destination, out int charsWritten, "G")); + Assert.Equal("", destination.ToString()); + Assert.Equal(0, charsWritten); + } + + [Theory] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(" \t")] + [InlineData("a")] + [InlineData("ab")] + [InlineData("abc")] + [InlineData("gg")] + [InlineData("dd")] + [InlineData("xx")] + [InlineData("ff")] + private static void TryFormat_WithInvalidFormat_ThrowsWithNoCharsWritten(string format) + { + SimpleEnum expecedEnum = SimpleEnum.Green; + string expected = nameof(expecedEnum); + int charsWritten = 0; + char[] destination = new char[expected.Length]; + + Assert.Throws(() => Enum.TryFormat(expecedEnum, destination, out charsWritten, format)); + Assert.Equal(new string('\0', expected.Length), new string(destination)); + Assert.Equal(0, charsWritten); + } + [Fact] public static void Format_Invalid() { @@ -2074,36 +2410,10 @@ public static void Format_Invalid() Assert.Throws(() => Enum.Format(typeof(SimpleEnum), SimpleEnum.Red, "t")); // No such format } - public static IEnumerable UnsupportedEnumType_TestData() - { - yield return new object[] { s_floatEnumType, 1.0f }; - yield return new object[] { s_doubleEnumType, 1.0 }; - yield return new object[] { s_intPtrEnumType, (IntPtr)1 }; - yield return new object[] { s_uintPtrEnumType, (UIntPtr)1 }; - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] - [MemberData(nameof(UnsupportedEnumType_TestData))] - public static void GetName_Unsupported_ThrowsArgumentException(Type enumType, object value) - { - AssertExtensions.Throws("value", () => Enum.GetName(enumType, value)); - } - - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] - [MemberData(nameof(UnsupportedEnumType_TestData))] - public static void IsDefined_UnsupportedEnumType_ThrowsInvalidOperationException(Type enumType, object value) - { - Exception ex = Assert.ThrowsAny(() => Enum.IsDefined(enumType, value)); - string exName = ex.GetType().Name; - Assert.True(exName == nameof(InvalidOperationException) || exName == "ContractException"); - } - public static IEnumerable UnsupportedEnum_TestData() { yield return new object[] { Enum.ToObject(s_floatEnumType, 1) }; yield return new object[] { Enum.ToObject(s_doubleEnumType, 2) }; - yield return new object[] { Enum.ToObject(s_intPtrEnumType, 1) }; - yield return new object[] { Enum.ToObject(s_uintPtrEnumType, 2) }; } [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] @@ -2115,23 +2425,6 @@ public static void ToString_UnsupportedEnumType_ThrowsArgumentException(Enum e) Assert.True(formatXExceptionName == nameof(InvalidOperationException) || formatXExceptionName == "ContractException"); } - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] - [MemberData(nameof(UnsupportedEnumType_TestData))] - public static void Format_UnsupportedEnumType_ThrowsArgumentException(Type enumType, object value) - { - Exception formatGException = Assert.ThrowsAny(() => Enum.Format(enumType, value, "G")); - string formatGExceptionName = formatGException.GetType().Name; - Assert.True(formatGExceptionName == nameof(InvalidOperationException) || formatGExceptionName == "ContractException"); - - Exception formatXException = Assert.ThrowsAny(() => Enum.Format(enumType, value, "X")); - string formatXExceptionName = formatXException.GetType().Name; - Assert.True(formatXExceptionName == nameof(InvalidOperationException) || formatXExceptionName == "ContractException"); - - Exception formatFException = Assert.ThrowsAny(() => Enum.Format(enumType, value, "F")); - string formatFExceptionName = formatFException.GetType().Name; - Assert.True(formatFExceptionName == nameof(InvalidOperationException) || formatFExceptionName == "ContractException"); - } - private static EnumBuilder GetNonRuntimeEnumTypeBuilder(Type underlyingType) { if (!PlatformDetection.IsReflectionEmitSupported) diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs index 91ce969aa61af..9f240dead871b 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs @@ -977,7 +977,7 @@ public void WaitForStatus(ServiceControllerStatus desiredStatus) /// Wait for specific timeout public void WaitForStatus(ServiceControllerStatus desiredStatus, TimeSpan timeout) { - if (!Enum.IsDefined(typeof(ServiceControllerStatus), desiredStatus)) + if (!Enum.IsDefined(desiredStatus)) throw new ArgumentException(SR.Format(SR.InvalidEnumArgument, nameof(desiredStatus), (int)desiredStatus, typeof(ServiceControllerStatus))); DateTime start = DateTime.UtcNow; diff --git a/src/mono/System.Private.CoreLib/src/System/Enum.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Enum.Mono.cs index 765a2865f60ce..8a6d16a2067a1 100644 --- a/src/mono/System.Private.CoreLib/src/System/Enum.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Enum.Mono.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; @@ -9,7 +11,7 @@ namespace System public partial class Enum { [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool GetEnumValuesAndNames(QCallTypeHandle enumType, out ulong[] values, out string[] names); + private static extern void GetEnumValuesAndNames(QCallTypeHandle enumType, out ulong[] values, out string[] names); [MethodImpl(MethodImplOptions.InternalCall)] private static extern void InternalBoxEnum(QCallTypeHandle enumType, ObjectHandleOnStack res, long value); @@ -27,6 +29,12 @@ private static object InternalBoxEnum(RuntimeType enumType, long value) return res!; } + private static unsafe CorElementType InternalGetCorElementType(RuntimeType rt) + { + Debug.Assert(rt.IsActualEnum); + return InternalGetCorElementType(new QCallTypeHandle(ref rt)); + } + private CorElementType InternalGetCorElementType() { RuntimeType this_type = (RuntimeType)GetType(); @@ -40,17 +48,66 @@ internal static RuntimeType InternalGetUnderlyingType(RuntimeType enumType) return res!; } - private static EnumInfo GetEnumInfo(RuntimeType enumType, bool getNames = true) + private static unsafe EnumInfo GetEnumInfo(RuntimeType enumType, bool getNames = true) + where TUnderlyingValue : struct, INumber { - EnumInfo? entry = enumType.Cache.EnumInfo; + EnumInfo? entry = enumType.Cache.EnumInfo as EnumInfo; + Debug.Assert(entry is null || entry.Names is not null); - if (entry == null || (getNames && entry.Names == null)) + if (entry == null) { - if (!GetEnumValuesAndNames(new QCallTypeHandle(ref enumType), out ulong[]? values, out string[]? names)) - Array.Sort(values, names, Collections.Generic.Comparer.Default); + GetEnumValuesAndNames(new QCallTypeHandle(ref enumType), out ulong[]? uint64Values, out string[]? names); + Debug.Assert(names is not null); + Debug.Assert(uint64Values is not null); + + TUnderlyingValue[] values; + if (typeof(TUnderlyingValue) == typeof(ulong)) + { + values = (TUnderlyingValue[])(object)uint64Values; + } + else + { +#pragma warning disable 8500 // pointer to / sizeof managed types + values = new TUnderlyingValue[uint64Values.Length]; + switch (sizeof(TUnderlyingValue)) + { + case 1: + for (int i = 0; i < values.Length; i++) + { + byte value = (byte)uint64Values[i]; + values[i] = *(TUnderlyingValue*)(&value); + } + break; + + case 2: + for (int i = 0; i < values.Length; i++) + { + ushort value = (ushort)uint64Values[i]; + values[i] = *(TUnderlyingValue*)(&value); + } + break; + + case 4: + for (int i = 0; i < values.Length; i++) + { + uint value = (uint)uint64Values[i]; + values[i] = *(TUnderlyingValue*)(&value); + } + break; + + case 8: + for (int i = 0; i < values.Length; i++) + { + ulong value = uint64Values[i]; + values[i] = *(TUnderlyingValue*)(&value); + } + break; + } +#pragma warning restore 8500 + } bool hasFlagsAttribute = enumType.IsDefined(typeof(FlagsAttribute), inherit: false); - entry = new EnumInfo(hasFlagsAttribute, values, names); + entry = new EnumInfo(hasFlagsAttribute, values, names); enumType.Cache.EnumInfo = entry; } diff --git a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs index 6a7c155b92f05..a8d528460d8e2 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -1551,7 +1551,7 @@ private void CreateInstanceCheckThis() internal sealed class TypeCache { - public Enum.EnumInfo? EnumInfo; + public object? EnumInfo; public TypeCode TypeCode; // this is the displayed form: special characters // ,+*&*[]\ in the identifier portions of the names diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h index f61cc2b688bed..467570a2f7468 100644 --- a/src/mono/mono/metadata/icall-def.h +++ b/src/mono/mono/metadata/icall-def.h @@ -185,7 +185,7 @@ NOHANDLES(ICALL(NATIVE_RUNTIME_EVENT_SOURCE_8, "LogThreadPoolWorkerThreadWait", NOHANDLES(ICALL(NATIVE_RUNTIME_EVENT_SOURCE_9, "LogThreadPoolWorkingThreadCount", ves_icall_System_Diagnostics_Tracing_NativeRuntimeEventSource_LogThreadPoolWorkingThreadCount)) ICALL_TYPE(ENUM, "System.Enum", ENUM_1) -HANDLES(ENUM_1, "GetEnumValuesAndNames", ves_icall_System_Enum_GetEnumValuesAndNames, MonoBoolean, 3, (MonoQCallTypeHandle, MonoArrayOut, MonoArrayOut)) +HANDLES(ENUM_1, "GetEnumValuesAndNames", ves_icall_System_Enum_GetEnumValuesAndNames, void, 3, (MonoQCallTypeHandle, MonoArrayOut, MonoArrayOut)) HANDLES(ENUM_2, "InternalBoxEnum", ves_icall_System_Enum_InternalBoxEnum, void, 3, (MonoQCallTypeHandle, MonoObjectHandleOnStack, guint64)) NOHANDLES(ICALL(ENUM_3, "InternalGetCorElementType", ves_icall_System_Enum_InternalGetCorElementType)) HANDLES(ENUM_4, "InternalGetUnderlyingType", ves_icall_System_Enum_InternalGetUnderlyingType, void, 2, (MonoQCallTypeHandle, MonoObjectHandleOnStack)) diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index a80047aaec4ec..75904f5ad1db1 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -3678,7 +3678,7 @@ ves_icall_System_Enum_InternalGetCorElementType (MonoQCallTypeHandle type_handle } static void -get_enum_field (MonoArrayHandle names, MonoArrayHandle values, int base_type, MonoClassField *field, guint* j, guint64 *previous_value, gboolean *sorted, MonoError *error) +get_enum_field (MonoArrayHandle names, MonoArrayHandle values, int base_type, MonoClassField *field, guint* j, MonoError *error) { HANDLE_FUNCTION_ENTER(); guint64 field_value; @@ -3702,16 +3702,12 @@ get_enum_field (MonoArrayHandle names, MonoArrayHandle values, int base_type, Mo field_value = read_enum_value (p, base_type); MONO_HANDLE_ARRAY_SETVAL (values, guint64, *j, field_value); - if (*previous_value > field_value) - *sorted = FALSE; - - *previous_value = field_value; (*j)++; leave: HANDLE_FUNCTION_RETURN(); } -MonoBoolean +void ves_icall_System_Enum_GetEnumValuesAndNames (MonoQCallTypeHandle type_handle, MonoArrayHandleOut values, MonoArrayHandleOut names, MonoError *error) { MonoClass *enumc = mono_class_from_mono_type_internal (type_handle.type); @@ -3719,34 +3715,30 @@ ves_icall_System_Enum_GetEnumValuesAndNames (MonoQCallTypeHandle type_handle, Mo gpointer iter; MonoClassField *field; int base_type; - guint64 previous_value = 0; - gboolean sorted = TRUE; mono_class_init_checked (enumc, error); - return_val_if_nok (error, FALSE); + return_if_nok (error); if (!m_class_is_enumtype (enumc)) { mono_error_set_argument (error, NULL, "Type provided must be an Enum."); - return TRUE; + return; } base_type = mono_class_enum_basetype_internal (enumc)->type; nvalues = mono_class_num_fields (enumc) > 0 ? mono_class_num_fields (enumc) - 1 : 0; MONO_HANDLE_ASSIGN(names, mono_array_new_handle (mono_defaults.string_class, nvalues, error)); - return_val_if_nok (error, FALSE); + return_if_nok (error); MONO_HANDLE_ASSIGN(values, mono_array_new_handle (mono_defaults.uint64_class, nvalues, error)); - return_val_if_nok (error, FALSE); + return_if_nok (error); iter = NULL; while ((field = mono_class_get_fields_internal (enumc, &iter))) { - get_enum_field (names, values, base_type, field, &j, &previous_value, &sorted, error); + get_enum_field (names, values, base_type, field, &j, error); if (!is_ok (error)) break; } - return_val_if_nok (error, FALSE); - - return sorted || base_type == MONO_TYPE_R4 || base_type == MONO_TYPE_R8; + return_if_nok (error); } enum {