Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit f6cd99c

Browse files
jamesqojkotas
authored andcommitted
Specialize Comparer<T> for enums to avoid boxing (#5503)
1 parent de69718 commit f6cd99c

File tree

1 file changed

+188
-27
lines changed

1 file changed

+188
-27
lines changed

src/mscorlib/src/System/Collections/Generic/Comparer.cs

Lines changed: 188 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Diagnostics.Contracts;
1111
//using System.Globalization;
1212
using System.Runtime.CompilerServices;
13+
using System.Runtime.Serialization;
1314

1415
namespace System.Collections.Generic
1516
{
@@ -61,6 +62,36 @@ private static Comparer<T> CreateComparer()
6162
}
6263
}
6364
}
65+
else if (t.IsEnum)
66+
{
67+
// Explicitly call Enum.GetUnderlyingType here. Although GetTypeCode
68+
// ends up doing this anyway, we end up avoiding an unnecessary P/Invoke
69+
// and virtual method call.
70+
TypeCode underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(t));
71+
72+
// Depending on the enum type, we need to special case the comparers so that we avoid boxing
73+
// Specialize differently for signed/unsigned types so we avoid problems with large numbers
74+
switch (underlyingTypeCode)
75+
{
76+
case TypeCode.SByte:
77+
case TypeCode.Int16:
78+
case TypeCode.Int32:
79+
result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(Int32EnumComparer<int>), t);
80+
break;
81+
case TypeCode.Byte:
82+
case TypeCode.UInt16:
83+
case TypeCode.UInt32:
84+
result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(UInt32EnumComparer<uint>), t);
85+
break;
86+
// 64-bit enums: use UnsafeEnumCastLong
87+
case TypeCode.Int64:
88+
result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(Int64EnumComparer<long>), t);
89+
break;
90+
case TypeCode.UInt64:
91+
result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(UInt64EnumComparer<ulong>), t);
92+
break;
93+
}
94+
}
6495

6596
return result != null ?
6697
(Comparer<T>)result :
@@ -77,9 +108,15 @@ int IComparer.Compare(object x, object y) {
77108
return 0;
78109
}
79110
}
80-
111+
112+
// Note: although there is a lot of shared code in the following
113+
// comparers, we do not incorporate it into a base class for perf
114+
// reasons. Adding another base class (even one with no fields)
115+
// means another generic instantiation, which can be costly esp.
116+
// for value types.
117+
81118
[Serializable]
82-
internal class GenericComparer<T> : Comparer<T> where T: IComparable<T>
119+
internal sealed class GenericComparer<T> : Comparer<T> where T : IComparable<T>
83120
{
84121
public override int Compare(T x, T y) {
85122
if (x != null) {
@@ -91,18 +128,15 @@ public override int Compare(T x, T y) {
91128
}
92129

93130
// Equals method for the comparer itself.
94-
public override bool Equals(Object obj){
95-
GenericComparer<T> comparer = obj as GenericComparer<T>;
96-
return comparer != null;
97-
}
131+
public override bool Equals(Object obj) =>
132+
obj != null && GetType() == obj.GetType();
98133

99-
public override int GetHashCode() {
100-
return this.GetType().Name.GetHashCode();
101-
}
134+
public override int GetHashCode() =>
135+
GetType().GetHashCode();
102136
}
103137

104138
[Serializable]
105-
internal class NullableComparer<T> : Comparer<Nullable<T>> where T : struct, IComparable<T>
139+
internal sealed class NullableComparer<T> : Comparer<T?> where T : struct, IComparable<T>
106140
{
107141
public override int Compare(Nullable<T> x, Nullable<T> y) {
108142
if (x.HasValue) {
@@ -114,37 +148,30 @@ public override int Compare(Nullable<T> x, Nullable<T> y) {
114148
}
115149

116150
// Equals method for the comparer itself.
117-
public override bool Equals(Object obj){
118-
NullableComparer<T> comparer = obj as NullableComparer<T>;
119-
return comparer != null;
120-
}
121-
151+
public override bool Equals(Object obj) =>
152+
obj != null && GetType() == obj.GetType();
122153

123-
public override int GetHashCode() {
124-
return this.GetType().Name.GetHashCode();
125-
}
154+
public override int GetHashCode() =>
155+
GetType().GetHashCode();
126156
}
127157

128158
[Serializable]
129-
internal class ObjectComparer<T> : Comparer<T>
159+
internal sealed class ObjectComparer<T> : Comparer<T>
130160
{
131161
public override int Compare(T x, T y) {
132162
return System.Collections.Comparer.Default.Compare(x, y);
133163
}
134164

135165
// Equals method for the comparer itself.
136-
public override bool Equals(Object obj){
137-
ObjectComparer<T> comparer = obj as ObjectComparer<T>;
138-
return comparer != null;
139-
}
166+
public override bool Equals(Object obj) =>
167+
obj != null && GetType() == obj.GetType();
140168

141-
public override int GetHashCode() {
142-
return this.GetType().Name.GetHashCode();
143-
}
169+
public override int GetHashCode() =>
170+
GetType().GetHashCode();
144171
}
145172

146173
[Serializable]
147-
internal class ComparisonComparer<T> : Comparer<T>
174+
internal sealed class ComparisonComparer<T> : Comparer<T>
148175
{
149176
private readonly Comparison<T> _comparison;
150177

@@ -156,4 +183,138 @@ public override int Compare(T x, T y) {
156183
return _comparison(x, y);
157184
}
158185
}
186+
187+
// Enum comparers (specialized to avoid boxing)
188+
// NOTE: Each of these needs to implement ISerializable
189+
// and have a SerializationInfo/StreamingContext ctor,
190+
// since we want to serialize as ObjectComparer for
191+
// back-compat reasons (see below).
192+
193+
[Serializable]
194+
internal sealed class Int32EnumComparer<T> : Comparer<T>, ISerializable where T : struct
195+
{
196+
public Int32EnumComparer()
197+
{
198+
Contract.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!");
199+
}
200+
201+
// Used by the serialization engine.
202+
private Int32EnumComparer(SerializationInfo info, StreamingContext context) { }
203+
204+
public override int Compare(T x, T y)
205+
{
206+
int ix = JitHelpers.UnsafeEnumCast(x);
207+
int iy = JitHelpers.UnsafeEnumCast(y);
208+
return ix.CompareTo(iy);
209+
}
210+
211+
// Equals method for the comparer itself.
212+
public override bool Equals(Object obj) =>
213+
obj != null && GetType() == obj.GetType();
214+
215+
public override int GetHashCode() =>
216+
GetType().GetHashCode();
217+
218+
public void GetObjectData(SerializationInfo info, StreamingContext context)
219+
{
220+
// Previously Comparer<T> was not specialized for enums,
221+
// and instead fell back to ObjectComparer which uses boxing.
222+
// Set the type as ObjectComparer here so code that serializes
223+
// Comparer for enums will not break.
224+
info.SetType(typeof(ObjectComparer<T>));
225+
}
226+
}
227+
228+
[Serializable]
229+
internal sealed class UInt32EnumComparer<T> : Comparer<T>, ISerializable where T : struct
230+
{
231+
public UInt32EnumComparer()
232+
{
233+
Contract.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!");
234+
}
235+
236+
// Used by the serialization engine.
237+
private UInt32EnumComparer(SerializationInfo info, StreamingContext context) { }
238+
239+
public override int Compare(T x, T y)
240+
{
241+
uint ix = (uint)JitHelpers.UnsafeEnumCast(x);
242+
uint iy = (uint)JitHelpers.UnsafeEnumCast(y);
243+
return ix.CompareTo(iy);
244+
}
245+
246+
// Equals method for the comparer itself.
247+
public override bool Equals(Object obj) =>
248+
obj != null && GetType() == obj.GetType();
249+
250+
public override int GetHashCode() =>
251+
GetType().GetHashCode();
252+
253+
public void GetObjectData(SerializationInfo info, StreamingContext context)
254+
{
255+
info.SetType(typeof(ObjectComparer<T>));
256+
}
257+
}
258+
259+
[Serializable]
260+
internal sealed class Int64EnumComparer<T> : Comparer<T>, ISerializable where T : struct
261+
{
262+
public Int64EnumComparer()
263+
{
264+
Contract.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!");
265+
}
266+
267+
// Used by the serialization engine.
268+
private Int64EnumComparer(SerializationInfo info, StreamingContext context) { }
269+
270+
public override int Compare(T x, T y)
271+
{
272+
long lx = JitHelpers.UnsafeEnumCastLong(x);
273+
long ly = JitHelpers.UnsafeEnumCastLong(y);
274+
return lx.CompareTo(ly);
275+
}
276+
277+
// Equals method for the comparer itself.
278+
public override bool Equals(Object obj) =>
279+
obj != null && GetType() == obj.GetType();
280+
281+
public override int GetHashCode() =>
282+
GetType().GetHashCode();
283+
284+
public void GetObjectData(SerializationInfo info, StreamingContext context)
285+
{
286+
info.SetType(typeof(ObjectComparer<T>));
287+
}
288+
}
289+
290+
[Serializable]
291+
internal sealed class UInt64EnumComparer<T> : Comparer<T>, ISerializable where T : struct
292+
{
293+
public UInt64EnumComparer()
294+
{
295+
Contract.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!");
296+
}
297+
298+
// Used by the serialization engine.
299+
private UInt64EnumComparer(SerializationInfo info, StreamingContext context) { }
300+
301+
public override int Compare(T x, T y)
302+
{
303+
ulong lx = (ulong)JitHelpers.UnsafeEnumCastLong(x);
304+
ulong ly = (ulong)JitHelpers.UnsafeEnumCastLong(y);
305+
return lx.CompareTo(ly);
306+
}
307+
308+
// Equals method for the comparer itself.
309+
public override bool Equals(Object obj) =>
310+
obj != null && GetType() == obj.GetType();
311+
312+
public override int GetHashCode() =>
313+
GetType().GetHashCode();
314+
315+
public void GetObjectData(SerializationInfo info, StreamingContext context)
316+
{
317+
info.SetType(typeof(ObjectComparer<T>));
318+
}
319+
}
159320
}

0 commit comments

Comments
 (0)