// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; //using System.Globalization; using System.Runtime.CompilerServices; using System.Security; using System.Runtime.Serialization; namespace System.Collections.Generic { [Serializable] [TypeDependencyAttribute("System.Collections.Generic.ObjectComparer`1")] public abstract class Comparer : IComparer, IComparer { static readonly Comparer defaultComparer = CreateComparer(); public static Comparer Default { get { Contract.Ensures(Contract.Result>() != null); return defaultComparer; } } public static Comparer Create(Comparison comparison) { Contract.Ensures(Contract.Result>() != null); if (comparison == null) throw new ArgumentNullException(nameof(comparison)); return new ComparisonComparer(comparison); } // // Note that logic in this method is replicated in vm\compile.cpp to ensure that NGen // saves the right instantiations // private static Comparer CreateComparer() { object result = null; RuntimeType t = (RuntimeType)typeof(T); // If T implements IComparable return a GenericComparer if (typeof(IComparable).IsAssignableFrom(t)) { result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericComparer), t); } else if (default(T) == null) { // If T is a Nullable where U implements IComparable return a NullableComparer if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { RuntimeType u = (RuntimeType)t.GetGenericArguments()[0]; if (typeof(IComparable<>).MakeGenericType(u).IsAssignableFrom(u)) { result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableComparer), u); } } } else if (t.IsEnum) { // Explicitly call Enum.GetUnderlyingType here. Although GetTypeCode // ends up doing this anyway, we end up avoiding an unnecessary P/Invoke // and virtual method call. TypeCode underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(t)); // Depending on the enum type, we need to special case the comparers so that we avoid boxing // Specialize differently for signed/unsigned types so we avoid problems with large numbers switch (underlyingTypeCode) { case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(Int32EnumComparer), t); break; case TypeCode.Byte: case TypeCode.UInt16: case TypeCode.UInt32: result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(UInt32EnumComparer), t); break; // 64-bit enums: use UnsafeEnumCastLong case TypeCode.Int64: result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(Int64EnumComparer), t); break; case TypeCode.UInt64: result = RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(UInt64EnumComparer), t); break; } } return result != null ? (Comparer)result : new ObjectComparer(); // Fallback to ObjectComparer, which uses boxing } public abstract int Compare(T x, T y); int IComparer.Compare(object x, object y) { if (x == null) return y == null ? 0 : -1; if (y == null) return 1; if (x is T && y is T) return Compare((T)x, (T)y); ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArgumentForComparison); return 0; } } // Note: although there is a lot of shared code in the following // comparers, we do not incorporate it into a base class for perf // reasons. Adding another base class (even one with no fields) // means another generic instantiation, which can be costly esp. // for value types. [Serializable] internal sealed class GenericComparer : Comparer where T : IComparable { public override int Compare(T x, T y) { if (x != null) { if (y != null) return x.CompareTo(y); return 1; } if (y != null) return -1; return 0; } // Equals method for the comparer itself. public override bool Equals(Object obj) => obj != null && GetType() == obj.GetType(); public override int GetHashCode() => GetType().GetHashCode(); } [Serializable] internal sealed class NullableComparer : Comparer where T : struct, IComparable { public override int Compare(T? x, T? y) { if (x.HasValue) { if (y.HasValue) return x.value.CompareTo(y.value); return 1; } if (y.HasValue) return -1; return 0; } // Equals method for the comparer itself. public override bool Equals(Object obj) => obj != null && GetType() == obj.GetType(); public override int GetHashCode() => GetType().GetHashCode(); } [Serializable] internal sealed class ObjectComparer : Comparer { public override int Compare(T x, T y) { return System.Collections.Comparer.Default.Compare(x, y); } // Equals method for the comparer itself. public override bool Equals(Object obj) => obj != null && GetType() == obj.GetType(); public override int GetHashCode() => GetType().GetHashCode(); } [Serializable] internal sealed class ComparisonComparer : Comparer { private readonly Comparison _comparison; public ComparisonComparer(Comparison comparison) { _comparison = comparison; } public override int Compare(T x, T y) { return _comparison(x, y); } } // Enum comparers (specialized to avoid boxing) // NOTE: Each of these needs to implement ISerializable // and have a SerializationInfo/StreamingContext ctor, // since we want to serialize as ObjectComparer for // back-compat reasons (see below). [Serializable] internal sealed class Int32EnumComparer : Comparer, ISerializable where T : struct { public Int32EnumComparer() { Debug.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!"); } // Used by the serialization engine. private Int32EnumComparer(SerializationInfo info, StreamingContext context) { } public override int Compare(T x, T y) { int ix = JitHelpers.UnsafeEnumCast(x); int iy = JitHelpers.UnsafeEnumCast(y); return ix.CompareTo(iy); } // Equals method for the comparer itself. public override bool Equals(Object obj) => obj != null && GetType() == obj.GetType(); public override int GetHashCode() => GetType().GetHashCode(); public void GetObjectData(SerializationInfo info, StreamingContext context) { // Previously Comparer was not specialized for enums, // and instead fell back to ObjectComparer which uses boxing. // Set the type as ObjectComparer here so code that serializes // Comparer for enums will not break. info.SetType(typeof(ObjectComparer)); } } [Serializable] internal sealed class UInt32EnumComparer : Comparer, ISerializable where T : struct { public UInt32EnumComparer() { Debug.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!"); } // Used by the serialization engine. private UInt32EnumComparer(SerializationInfo info, StreamingContext context) { } public override int Compare(T x, T y) { uint ix = (uint)JitHelpers.UnsafeEnumCast(x); uint iy = (uint)JitHelpers.UnsafeEnumCast(y); return ix.CompareTo(iy); } // Equals method for the comparer itself. public override bool Equals(Object obj) => obj != null && GetType() == obj.GetType(); public override int GetHashCode() => GetType().GetHashCode(); public void GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(ObjectComparer)); } } [Serializable] internal sealed class Int64EnumComparer : Comparer, ISerializable where T : struct { public Int64EnumComparer() { Debug.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!"); } // Used by the serialization engine. private Int64EnumComparer(SerializationInfo info, StreamingContext context) { } public override int Compare(T x, T y) { long lx = JitHelpers.UnsafeEnumCastLong(x); long ly = JitHelpers.UnsafeEnumCastLong(y); return lx.CompareTo(ly); } // Equals method for the comparer itself. public override bool Equals(Object obj) => obj != null && GetType() == obj.GetType(); public override int GetHashCode() => GetType().GetHashCode(); public void GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(ObjectComparer)); } } [Serializable] internal sealed class UInt64EnumComparer : Comparer, ISerializable where T : struct { public UInt64EnumComparer() { Debug.Assert(typeof(T).IsEnum, "This type is only intended to be used to compare enums!"); } // Used by the serialization engine. private UInt64EnumComparer(SerializationInfo info, StreamingContext context) { } public override int Compare(T x, T y) { ulong lx = (ulong)JitHelpers.UnsafeEnumCastLong(x); ulong ly = (ulong)JitHelpers.UnsafeEnumCastLong(y); return lx.CompareTo(ly); } // Equals method for the comparer itself. public override bool Equals(Object obj) => obj != null && GetType() == obj.GetType(); public override int GetHashCode() => GetType().GetHashCode(); public void GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(ObjectComparer)); } } }