1+ using System . Diagnostics ;
12using System . Runtime . CompilerServices ;
23
34namespace Architect . DomainModeling . Enums ;
45
56internal static class InternalEnumExtensions
67{
7- private static readonly byte DefaultUndefinedValue = 191 ; // Greatest prime under 3/4 of Byte.MaxValue
8- private static readonly ushort FallbackUndefinedValue = 49139 ; // Greatest prime under 3/4 of UInt16.MaxValue
9-
8+ private static readonly ulong DefaultUndefinedValue = 191 ; // Greatest prime under 3/4 of Byte.MaxValue
9+ private static readonly ulong FallbackUndefinedValue = 49139 ; // Greatest prime under 3/4 of UInt16.MaxValue
10+
1011 /// <summary>
1112 /// <para>
1213 /// Attempts to return one of a small set of predefined values if one is undefined for <typeparamref name="TEnum"/>.
1314 /// </para>
1415 /// <para>
15- /// Does not accounts for the <see cref="FlagsAttribute"/>.
16+ /// Does not account for the <see cref="FlagsAttribute"/>.
1617 /// </para>
1718 /// </summary>
1819 public static bool TryGetUndefinedValueFast < TEnum > ( out TEnum value )
1920 where TEnum : unmanaged, Enum
20- {
21- var defaultUndefined = Unsafe . As < byte , TEnum > ( ref Unsafe . AsRef ( in DefaultUndefinedValue ) ) ;
22- if ( ! Enum . IsDefined ( defaultUndefined ) )
23- {
24- value = defaultUndefined ;
21+ {
22+ value = GetEnumValue < TEnum > ( DefaultUndefinedValue ) ;
23+ if ( ! Enum . IsDefined ( value ) )
2524 return true ;
26- }
27-
28- var fallbackUndefined = Unsafe . As < ushort , TEnum > ( ref Unsafe . AsRef ( in FallbackUndefinedValue ) ) ;
29- if ( Unsafe . SizeOf < TEnum > ( ) >= 2 && ! Enum . IsDefined ( fallbackUndefined ) )
30- {
31- value = fallbackUndefined ;
25+
26+ value = GetEnumValue < TEnum > ( FallbackUndefinedValue ) ;
27+ if ( Unsafe . SizeOf < TEnum > ( ) >= 2 && ! Enum . IsDefined ( value ) )
3228 return true ;
33- }
3429
3530 value = default ;
3631 return false ;
37- }
38-
32+ }
33+
3934 /// <summary>
4035 /// <para>
4136 /// Attempts to find an undefined value for <typeparamref name="TEnum"/>.
4237 /// </para>
4338 /// <para>
44- /// Does not accounts for the <see cref="FlagsAttribute"/>.
39+ /// Does not account for the <see cref="FlagsAttribute"/>.
4540 /// </para>
4641 /// </summary>
4742 public static bool TryGetUndefinedValue < TEnum > ( out TEnum value )
@@ -51,17 +46,17 @@ public static bool TryGetUndefinedValue<TEnum>(out TEnum value)
5146 return true ;
5247
5348 var values = Enum . GetValues < TEnum > ( ) ;
54- System . Diagnostics . Debug . Assert ( values . Select ( GetBinaryValue ) . Order ( ) . SequenceEqual ( values . Select ( GetBinaryValue ) ) , "Enum.GetValues() was expected to return elements in binary order." ) ;
55-
56- // If we do not end with the binary maximum, then use that
57- var enumBinaryMax = ~ 0UL >> ( 64 - 8 * Unsafe . SizeOf < TEnum > ( ) ) ; // E.g. 64-0 bits for ulong/long, 64-32 for uint/int, and so on
49+ Debug . Assert ( values . Select ( GetBinaryValue ) . Order ( ) . SequenceEqual ( values . Select ( GetBinaryValue ) ) , "Enum.GetValues() was expected to return elements in binary order." ) ;
50+
51+ // If we do not end with the binary maximum, then use that
52+ var enumBinaryMax = ~ 0UL >> ( 64 - 8 * Unsafe . SizeOf < TEnum > ( ) ) ; // E.g. 64-0 bits for ulong/long, 64-32 for uint/int, and so on
5853 if ( values . Length == 0 || values [ ^ 1 ] . GetBinaryValue ( ) < enumBinaryMax )
5954 {
60- value = Unsafe . As < ulong , TEnum > ( ref enumBinaryMax ) ;
55+ value = GetEnumValue < TEnum > ( enumBinaryMax ) ;
6156 return true ;
62- }
63-
64- // If we do not start with the default, then use that
57+ }
58+
59+ // If we do not start with the default, then use that
6560 ulong previousValue ;
6661 if ( ( previousValue = values [ 0 ] . GetBinaryValue ( ) ) != 0UL )
6762 {
@@ -70,67 +65,86 @@ public static bool TryGetUndefinedValue<TEnum>(out TEnum value)
7065 }
7166
7267 foreach ( var definedValue in values . Skip ( 1 ) )
73- {
74- // If there is a gap between the current and previous item
68+ {
69+ // If there is a gap between the current and previous item
7570 var currentValue = definedValue . GetBinaryValue ( ) ;
7671 if ( currentValue > previousValue + 1 )
7772 {
78- previousValue ++ ;
79- value = Unsafe . As < ulong , TEnum > ( ref previousValue ) ;
73+ value = GetEnumValue < TEnum > ( previousValue + 1 ) ;
8074 return true ;
8175 }
8276 previousValue = currentValue ;
8377 }
8478
8579 value = default ;
8680 return false ;
87- }
88-
81+ }
82+
8983 /// <summary>
90- /// Returns the numeric value of the given <paramref name="enumValue"/>.
84+ /// Returns the numeric value of the given <paramref name="enumValue"/>.
85+ /// Unlike <see cref="GetBinaryValue"/>, this retains negative values for signed enum types.
9186 /// </summary>
9287 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
93- public static Int128 GetNumericValue < T > ( this T enumValue )
94- where T : unmanaged, Enum
95- {
96- // Optimized by JIT, as Type.GetTypeCode(T) is treated as a constant
97- return Type . GetTypeCode ( typeof ( T ) ) switch
98- {
99- TypeCode . Byte => ( Int128 ) Unsafe . As < T , byte > ( ref enumValue ) ,
100- TypeCode . SByte => ( Int128 ) Unsafe . As < T , sbyte > ( ref enumValue ) ,
101- TypeCode . Int16 => ( Int128 ) Unsafe . As < T , short > ( ref enumValue ) ,
102- TypeCode . UInt16 => ( Int128 ) Unsafe . As < T , ushort > ( ref enumValue ) ,
103- TypeCode . Int32 => ( Int128 ) Unsafe . As < T , int > ( ref enumValue ) ,
104- TypeCode . UInt32 => ( Int128 ) Unsafe . As < T , uint > ( ref enumValue ) ,
105- TypeCode . Int64 => ( Int128 ) Unsafe . As < T , long > ( ref enumValue ) ,
106- TypeCode . UInt64 => ( Int128 ) Unsafe . As < T , ulong > ( ref enumValue ) ,
107- _ => default ,
108- } ;
109- }
110-
88+ public static Int128 GetNumericValue < TEnum > ( this TEnum enumValue )
89+ where TEnum : unmanaged, Enum
90+ {
91+ // Branches optimized away by JIT, as Type.GetEnumUnderlyingType() is [Intrinsic] and treated as a constant
92+ if ( typeof ( TEnum ) . GetEnumUnderlyingType ( ) == typeof ( byte ) ) return ( Int128 ) Unsafe . As < TEnum , byte > ( ref enumValue ) ;
93+ if ( typeof ( TEnum ) . GetEnumUnderlyingType ( ) == typeof ( sbyte ) ) return ( Int128 ) Unsafe . As < TEnum , sbyte > ( ref enumValue ) ;
94+ if ( typeof ( TEnum ) . GetEnumUnderlyingType ( ) == typeof ( ushort ) ) return ( Int128 ) Unsafe . As < TEnum , ushort > ( ref enumValue ) ;
95+ if ( typeof ( TEnum ) . GetEnumUnderlyingType ( ) == typeof ( short ) ) return ( Int128 ) Unsafe . As < TEnum , short > ( ref enumValue ) ;
96+ if ( typeof ( TEnum ) . GetEnumUnderlyingType ( ) == typeof ( uint ) ) return ( Int128 ) Unsafe . As < TEnum , uint > ( ref enumValue ) ;
97+ if ( typeof ( TEnum ) . GetEnumUnderlyingType ( ) == typeof ( int ) ) return ( Int128 ) Unsafe . As < TEnum , int > ( ref enumValue ) ;
98+ if ( typeof ( TEnum ) . GetEnumUnderlyingType ( ) == typeof ( ulong ) ) return ( Int128 ) Unsafe . As < TEnum , ulong > ( ref enumValue ) ;
99+ if ( typeof ( TEnum ) . GetEnumUnderlyingType ( ) == typeof ( long ) ) return ( Int128 ) Unsafe . As < TEnum , long > ( ref enumValue ) ;
100+ throw new UnreachableException ( ) ;
101+ }
102+
111103 /// <summary>
112104 /// <para>
113105 /// Returns the binary value of the given <paramref name="enumValue"/>, contained in a <see cref="UInt64"/>.
114106 /// </para>
115- /// <para>
116- /// The original value's bytes can be retrieved by doing a cast or <see cref="Unsafe.As{TFrom, TTo} "/> to the original enum or underlying type .
107+ /// <para>
108+ /// This method is the inverse of <see cref="GetEnumValue "/>.
117109 /// </para>
118110 /// </summary>
119111 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
120- public static ulong GetBinaryValue < T > ( this T enumValue )
121- where T : unmanaged, Enum
112+ public static ulong GetBinaryValue < TEnum > ( this TEnum enumValue )
113+ where TEnum : unmanaged, Enum
122114 {
123- var result = 0UL ;
124-
125- // Since the actual value may be smaller than ulong's 8 bytes, we must align to the least significant byte
126- // This way, casting the ulong back to the original type gets back the exact original bytes
127- // On little -endian, that means aligning to the left of the bytes
128- // On big-endian, that means aligning to the right of the bytes
115+ var result = 0UL ;
116+
117+ // TEnum could be shorter than 8 bytes
118+ // Little-endian will automatically write into the least significant bytes, since those are on the left for little-endian
119+ // For big -endian, they are on the right, so we need to look at the rightmost portion of the ulong, depending on size of TEnum
120+ // For example, a ushort TEnum will look at the rightmost 2 bytes of the ulong
129121 if ( BitConverter . IsLittleEndian )
130122 Unsafe . WriteUnaligned ( ref Unsafe . As < ulong , byte > ( ref result ) , enumValue ) ;
131123 else
132- Unsafe . WriteUnaligned ( ref Unsafe . Add ( ref Unsafe . As < ulong , byte > ( ref result ) , sizeof ( ulong ) - Unsafe . SizeOf < T > ( ) ) , enumValue ) ;
124+ Unsafe . WriteUnaligned ( ref Unsafe . Add ( ref Unsafe . As < ulong , byte > ( ref result ) , sizeof ( ulong ) - Unsafe . SizeOf < TEnum > ( ) ) , enumValue ) ;
133125
134126 return result ;
127+ }
128+
129+ /// <summary>
130+ /// <para>
131+ /// Returns the <typeparamref name="TEnum"/> enum value of the given <paramref name="binaryValue"/>.
132+ /// </para>
133+ /// <para>
134+ /// This method is the inverse of <see cref="GetBinaryValue"/>.
135+ /// </para>
136+ /// </summary>
137+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
138+ public static TEnum GetEnumValue < TEnum > ( ulong binaryValue )
139+ where TEnum : unmanaged, Enum
140+ {
141+ // TEnum could be shorter than 8 bytes
142+ // Little-endian will automatically write into the least significant bytes, since those are on the left for little-endian
143+ // For big-endian, they are on the right, so we need to look at the rightmost portion of the ulong, depending on size of TEnum
144+ // For example, a ushort TEnum will look at the rightmost 2 bytes of the ulong
145+ if ( BitConverter . IsLittleEndian )
146+ return Unsafe . ReadUnaligned < TEnum > ( ref Unsafe . As < ulong , byte > ( ref binaryValue ) ) ;
147+ else
148+ return Unsafe . ReadUnaligned < TEnum > ( ref Unsafe . Add ( ref Unsafe . As < ulong , byte > ( ref binaryValue ) , sizeof ( ulong ) - Unsafe . SizeOf < TEnum > ( ) ) ) ;
135149 }
136- }
150+ }
0 commit comments