From 9c12d8653942b675e2f9e413929423484f53ebb8 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sat, 29 Jun 2024 23:32:46 -0400 Subject: [PATCH 1/5] Implement `IAlternateEqualityComparer, string>` on `EqualityComparer.Default` --- .../Collections/Generic/ComparerHelpers.cs | 2 +- .../EqualityComparerHelpers.cs | 10 +++++ .../Generic/EqualityComparer.NativeAot.cs | 6 +++ src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/jitinterface.cpp | 18 ++++---- src/coreclr/vm/typehandle.cpp | 7 +++ src/coreclr/vm/typehandle.h | 3 ++ .../ConcurrentDictionaryTests.cs | 24 +++++------ .../FrozenDictionaryAlternateLookupTests.cs | 14 +----- .../Frozen/FrozenSetAlternateLookupTests.cs | 6 +-- .../Generic/CollectionExtensionsTests.cs | 26 +++++------ .../OutOfBoundsRegression.cs | 13 +++--- .../Collections/Generic/EqualityComparer.cs | 43 +++++++++++++++++++ .../NonRandomizedStringEqualityComparer.cs | 23 +--------- .../src/System/StringComparer.cs | 2 +- .../Generic/EqualityComparer.Mono.cs | 3 +- 16 files changed, 115 insertions(+), 86 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs index 87c07da9cdb38..954c8f90d230b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Collections/Generic/ComparerHelpers.cs @@ -68,7 +68,7 @@ internal static object CreateDefaultEqualityComparer(Type type) if (type == typeof(string)) { - return new GenericEqualityComparer(); + return new StringEqualityComparer(); } else if (type.IsAssignableTo(typeof(IEquatable<>).MakeGenericType(type))) { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/IntrinsicSupport/EqualityComparerHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/IntrinsicSupport/EqualityComparerHelpers.cs index 94592af9c8784..09a40452f432d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/IntrinsicSupport/EqualityComparerHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/IntrinsicSupport/EqualityComparerHelpers.cs @@ -51,6 +51,11 @@ internal static unsafe bool IsEnum(RuntimeTypeHandle t) return t.ToMethodTable()->IsEnum; } + internal static unsafe bool IsString(RuntimeTypeHandle t) + { + return t.ToMethodTable()->IsString; + } + // this function utilizes the template type loader to generate new // EqualityComparer types on the fly internal static object GetComparer(RuntimeTypeHandle t) @@ -59,6 +64,11 @@ internal static object GetComparer(RuntimeTypeHandle t) RuntimeTypeHandle openComparerType = default(RuntimeTypeHandle); RuntimeTypeHandle comparerTypeArgument = default(RuntimeTypeHandle); + if (IsString(t)) + { + return new StringEqualityComparer(); + } + if (RuntimeAugments.IsNullable(t)) { RuntimeTypeHandle nullableType = RuntimeAugments.GetNullableType(t); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.NativeAot.cs index 0b5c3d2914aa9..b7d79959332de 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.NativeAot.cs @@ -23,6 +23,12 @@ private static EqualityComparer Create() // This body serves as a fallback when instantiation-specific implementation is unavailable. // If that happens, the compiler ensures we generate data structures to make the fallback work // when this method is compiled. + + if (typeof(T) == typeof(string)) + { + return Unsafe.As>(new StringEqualityComparer()); + } + if (SupportsGenericIEquatableInterfaces) { return Unsafe.As>(EqualityComparerHelpers.GetComparer(typeof(T).TypeHandle)); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 0941e376d83e7..7ae4cbc710264 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1134,6 +1134,7 @@ DEFINE_METHOD(UTF8BUFFERMARSHALER, CONVERT_TO_MANAGED, ConvertToManaged, NoSig) // Classes referenced in EqualityComparer.Default optimization +DEFINE_CLASS(STRING_EQUALITYCOMPARER, CollectionsGeneric, StringEqualityComparer) DEFINE_CLASS(ENUM_EQUALITYCOMPARER, CollectionsGeneric, EnumEqualityComparer`1) DEFINE_CLASS(NULLABLE_EQUALITYCOMPARER, CollectionsGeneric, NullableEqualityComparer`1) DEFINE_CLASS(GENERIC_EQUALITYCOMPARER, CollectionsGeneric, GenericEqualityComparer`1) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 4b0180d2fd757..a33069ca969a1 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8848,8 +8848,7 @@ CORINFO_CLASS_HANDLE CEEInfo::getDefaultComparerClassHelper(CORINFO_CLASS_HANDLE TypeHandle elemTypeHnd(elemType); // Mirrors the logic in BCL's CompareHelpers.CreateDefaultComparer - // And in compile.cpp's SpecializeComparer - // + // We need to find the appropriate instantiation Instantiation inst(&elemTypeHnd, 1); @@ -8914,16 +8913,19 @@ CORINFO_CLASS_HANDLE CEEInfo::getDefaultEqualityComparerClassHelper(CORINFO_CLAS MODE_PREEMPTIVE; } CONTRACTL_END; - // Mirrors the logic in BCL's CompareHelpers.CreateDefaultEqualityComparer - // And in compile.cpp's SpecializeEqualityComparer + // Mirrors the logic in BCL's CompareHelpers.CreateDefaultEqualityComparer. TypeHandle elemTypeHnd(elemType); - // Mirrors the logic in BCL's CompareHelpers.CreateDefaultComparer - // And in compile.cpp's SpecializeComparer - // - // We need to find the appropriate instantiation + // We need to find the appropriate instantiation. Instantiation inst(&elemTypeHnd, 1); + // string + if (elemTypeHnd.IsString()) + { + TypeHandle resultTh = ((TypeHandle)CoreLibBinder::GetClass(CLASS__STRING_EQUALITYCOMPARER)).Instantiate(inst); + return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable()); + } + // Nullable if (Nullable::IsNullableType(elemTypeHnd)) { diff --git a/src/coreclr/vm/typehandle.cpp b/src/coreclr/vm/typehandle.cpp index d53b17a27ed6c..d5fea81cb4841 100644 --- a/src/coreclr/vm/typehandle.cpp +++ b/src/coreclr/vm/typehandle.cpp @@ -84,6 +84,13 @@ BOOL TypeHandle::IsArray() const { return !IsTypeDesc() && AsMethodTable()->IsArray(); } +BOOL TypeHandle::IsString() const +{ + LIMITED_METHOD_CONTRACT; + + return !IsTypeDesc() && AsMethodTable()->IsString(); +} + BOOL TypeHandle::IsGenericVariable() const { LIMITED_METHOD_DAC_CONTRACT; diff --git a/src/coreclr/vm/typehandle.h b/src/coreclr/vm/typehandle.h index 8abeb3972d38a..9e624629b72f6 100644 --- a/src/coreclr/vm/typehandle.h +++ b/src/coreclr/vm/typehandle.h @@ -463,6 +463,9 @@ class TypeHandle // PTR BOOL IsPointer() const; + // String + BOOL IsString() const; + // True if this type *is* a formal generic type parameter or any component of it is a formal generic type parameter BOOL ContainsGenericVariables(BOOL methodOnly=FALSE) const; diff --git a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs index 216ee55b41779..3302686ac06b6 100644 --- a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs @@ -1088,13 +1088,6 @@ public void ConcurrentWriteRead_NoTornValues() })); } - // TODO: Revise this test when EqualityComparer.Default implements IAlternateEqualityComparer, string> - [Fact] - public void GetAlternateLookup_FailsForDefaultComparer() - { - Assert.False(new ConcurrentDictionary().TryGetAlternateLookup>(out _)); - } - [Fact] public void GetAlternateLookup_FailsWhenIncompatible() { @@ -1119,17 +1112,19 @@ public void GetAlternateLookup_FailsWhenIncompatible() [InlineData(3)] [InlineData(4)] [InlineData(5)] + [InlineData(6)] public void GetAlternateLookup_OperationsMatchUnderlyingDictionary(int mode) { // Test with a variety of comparers to ensure that the alternate lookup is consistent with the underlying dictionary ConcurrentDictionary dictionary = new(mode switch { - 0 => StringComparer.Ordinal, - 1 => StringComparer.OrdinalIgnoreCase, - 2 => StringComparer.InvariantCulture, - 3 => StringComparer.InvariantCultureIgnoreCase, - 4 => StringComparer.CurrentCulture, - 5 => StringComparer.CurrentCultureIgnoreCase, + 0 => EqualityComparer.Default, + 1 => StringComparer.Ordinal, + 2 => StringComparer.OrdinalIgnoreCase, + 3 => StringComparer.InvariantCulture, + 4 => StringComparer.InvariantCultureIgnoreCase, + 5 => StringComparer.CurrentCulture, + 6 => StringComparer.CurrentCultureIgnoreCase, _ => throw new ArgumentOutOfRangeException(nameof(mode)) }); ConcurrentDictionary.AlternateLookup> lookup = dictionary.GetAlternateLookup>(); @@ -1165,7 +1160,8 @@ public void GetAlternateLookup_OperationsMatchUnderlyingDictionary(int mode) // Ensure that case-sensitivity of the comparer is respected lookup["a".AsSpan()] = 42; - if (dictionary.Comparer.Equals(StringComparer.Ordinal) || + if (dictionary.Comparer.Equals(EqualityComparer.Default) || + dictionary.Comparer.Equals(StringComparer.Ordinal) || dictionary.Comparer.Equals(StringComparer.InvariantCulture) || dictionary.Comparer.Equals(StringComparer.CurrentCulture)) { diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryAlternateLookupTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryAlternateLookupTests.cs index d09373c0dfba6..0b0ad5ad73475 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryAlternateLookupTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryAlternateLookupTests.cs @@ -12,20 +12,10 @@ public class FrozenDictionaryAlternateLookupTests [Fact] public void AlternateLookup_Empty() { - FrozenDictionary[] unsupported = + FrozenDictionary[] supported = [ - FrozenDictionary.Empty, FrozenDictionary.ToFrozenDictionary([]), FrozenDictionary.ToFrozenDictionary([], EqualityComparer.Default), - ]; - foreach (FrozenDictionary frozen in unsupported) - { - Assert.Throws(() => frozen.GetAlternateLookup>()); - Assert.False(frozen.TryGetAlternateLookup>(out _)); - } - - FrozenDictionary[] supported = - [ FrozenDictionary.ToFrozenDictionary([], StringComparer.Ordinal), FrozenDictionary.ToFrozenDictionary([], StringComparer.OrdinalIgnoreCase), ]; @@ -39,7 +29,7 @@ public void AlternateLookup_Empty() [Fact] public void UnsupportedComparer_ThrowsOrReturnsFalse() { - FrozenDictionary frozen = new Dictionary { ["a"] = 1, ["b"] = 2 }.ToFrozenDictionary(); + FrozenDictionary frozen = new Dictionary { ['a'] = 1, ['b'] = 2 }.ToFrozenDictionary(); Assert.Throws(() => frozen.GetAlternateLookup>()); Assert.False(frozen.TryGetAlternateLookup>(out _)); } diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetAlternateLookupTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetAlternateLookupTests.cs index 527dc327b37d5..559864e5cc84a 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetAlternateLookupTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetAlternateLookupTests.cs @@ -12,9 +12,9 @@ public class FrozenSetAlternateLookupTests [Fact] public void AlternateLookup_Empty() { - Assert.False(FrozenSet.Empty.TryGetAlternateLookup>(out _)); + Assert.True(FrozenSet.Empty.TryGetAlternateLookup>(out _)); - foreach (StringComparer comparer in new[] { StringComparer.Ordinal, StringComparer.OrdinalIgnoreCase }) + foreach (IEqualityComparer comparer in new IEqualityComparer[] { null, EqualityComparer.Default, StringComparer.Ordinal, StringComparer.OrdinalIgnoreCase }) { FrozenSet.AlternateLookup> lookup = FrozenSet.ToFrozenSet([], comparer).GetAlternateLookup>(); Assert.False(lookup.Contains("anything".AsSpan())); @@ -24,7 +24,7 @@ public void AlternateLookup_Empty() [Fact] public void UnsupportedComparer() { - FrozenSet frozen = FrozenSet.ToFrozenSet(["a", "b"]); + FrozenSet frozen = FrozenSet.ToFrozenSet(['a', 'b']); Assert.Throws(() => frozen.GetAlternateLookup>()); Assert.False(frozen.TryGetAlternateLookup>(out _)); } diff --git a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs index 50e7253deadd5..00333c6203495 100644 --- a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs +++ b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs @@ -151,15 +151,6 @@ public void GetAlternateLookup_ThrowsWhenNull() AssertExtensions.Throws("set", () => CollectionExtensions.TryGetAlternateLookup((HashSet)null, out _)); } - // TODO https://github.com/dotnet/runtime/issues/102906: - // Revise this test when EqualityComparer.Default implements IAlternateEqualityComparer, string> - [Fact] - public void GetAlternateLookup_FailsForDefaultComparer() - { - Assert.False(new Dictionary().TryGetAlternateLookup>(out _)); - Assert.False(new HashSet().TryGetAlternateLookup>(out _)); - } - [Fact] public void GetAlternateLookup_FailsWhenIncompatible() { @@ -196,17 +187,19 @@ public void GetAlternateLookup_FailsWhenIncompatible() [InlineData(3)] [InlineData(4)] [InlineData(5)] + [InlineData(6)] public void Dictionary_GetAlternateLookup_OperationsMatchUnderlyingDictionary(int mode) { // Test with a variety of comparers to ensure that the alternate lookup is consistent with the underlying dictionary Dictionary dictionary = new(mode switch { - 0 => StringComparer.Ordinal, - 1 => StringComparer.OrdinalIgnoreCase, - 2 => StringComparer.InvariantCulture, - 3 => StringComparer.InvariantCultureIgnoreCase, - 4 => StringComparer.CurrentCulture, - 5 => StringComparer.CurrentCultureIgnoreCase, + 0 => EqualityComparer.Default, + 1 => StringComparer.Ordinal, + 2 => StringComparer.OrdinalIgnoreCase, + 3 => StringComparer.InvariantCulture, + 4 => StringComparer.InvariantCultureIgnoreCase, + 5 => StringComparer.CurrentCulture, + 6 => StringComparer.CurrentCultureIgnoreCase, _ => throw new ArgumentOutOfRangeException(nameof(mode)) }); Dictionary.AlternateLookup> lookup = dictionary.GetAlternateLookup>(); @@ -241,7 +234,8 @@ public void Dictionary_GetAlternateLookup_OperationsMatchUnderlyingDictionary(in // Ensure that case-sensitivity of the comparer is respected lookup["a".AsSpan()] = 42; - if (dictionary.Comparer.Equals(StringComparer.Ordinal) || + if (dictionary.Comparer.Equals(EqualityComparer.Default) || + dictionary.Comparer.Equals(StringComparer.Ordinal) || dictionary.Comparer.Equals(StringComparer.InvariantCulture) || dictionary.Comparer.Equals(StringComparer.CurrentCulture)) { diff --git a/src/libraries/System.Collections/tests/Generic/Dictionary/HashCollisionScenarios/OutOfBoundsRegression.cs b/src/libraries/System.Collections/tests/Generic/Dictionary/HashCollisionScenarios/OutOfBoundsRegression.cs index 6fe06dd432e74..f0234d4ba42e9 100644 --- a/src/libraries/System.Collections/tests/Generic/Dictionary/HashCollisionScenarios/OutOfBoundsRegression.cs +++ b/src/libraries/System.Collections/tests/Generic/Dictionary/HashCollisionScenarios/OutOfBoundsRegression.cs @@ -18,7 +18,7 @@ public class InternalHashCodeTests_Dictionary_NullComparer : InternalHashCodeTes protected override bool ContainsKey(Dictionary collection, string key) => collection.ContainsKey(key); protected override IEqualityComparer GetComparer(Dictionary collection) => collection.Comparer; - protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedDefaultComparerType; + protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedOrdinalComparerType; protected override IEqualityComparer ExpectedPublicComparerBeforeCollisionThreshold => EqualityComparer.Default; protected override Type ExpectedInternalComparerTypeAfterCollisionThreshold => randomizedOrdinalComparerType; @@ -60,7 +60,7 @@ public class InternalHashCodeTests_Dictionary_DefaultComparer : InternalHashCode protected override bool ContainsKey(Dictionary collection, string key) => collection.ContainsKey(key); protected override IEqualityComparer GetComparer(Dictionary collection) => collection.Comparer; - protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedDefaultComparerType; + protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedOrdinalComparerType; protected override IEqualityComparer ExpectedPublicComparerBeforeCollisionThreshold => EqualityComparer.Default; protected override Type ExpectedInternalComparerTypeAfterCollisionThreshold => randomizedOrdinalComparerType; } @@ -124,7 +124,7 @@ public class InternalHashCodeTests_HashSet_NullComparer : InternalHashCodeTests< protected override bool ContainsKey(HashSet collection, string key) => collection.Contains(key); protected override IEqualityComparer GetComparer(HashSet collection) => collection.Comparer; - protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedDefaultComparerType; + protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedOrdinalComparerType; protected override IEqualityComparer ExpectedPublicComparerBeforeCollisionThreshold => EqualityComparer.Default; protected override Type ExpectedInternalComparerTypeAfterCollisionThreshold => randomizedOrdinalComparerType; } @@ -136,7 +136,7 @@ public class InternalHashCodeTests_HashSet_DefaultComparer : InternalHashCodeTes protected override bool ContainsKey(HashSet collection, string key) => collection.Contains(key); protected override IEqualityComparer GetComparer(HashSet collection) => collection.Comparer; - protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedDefaultComparerType; + protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedOrdinalComparerType; protected override IEqualityComparer ExpectedPublicComparerBeforeCollisionThreshold => EqualityComparer.Default; protected override Type ExpectedInternalComparerTypeAfterCollisionThreshold => randomizedOrdinalComparerType; } @@ -186,7 +186,7 @@ public class InternalHashCodeTests_OrderedDictionary_NullComparer : InternalHash protected override bool ContainsKey(OrderedDictionary collection, string key) => collection.ContainsKey(key); protected override IEqualityComparer GetComparer(OrderedDictionary collection) => collection.Comparer; - protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedDefaultComparerType; + protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedOrdinalComparerType; protected override IEqualityComparer ExpectedPublicComparerBeforeCollisionThreshold => EqualityComparer.Default; protected override Type ExpectedInternalComparerTypeAfterCollisionThreshold => EqualityComparer.Default.GetType(); } @@ -198,7 +198,7 @@ public class InternalHashCodeTests_OrderedDictionary_DefaultComparer : InternalH protected override bool ContainsKey(OrderedDictionary collection, string key) => collection.ContainsKey(key); protected override IEqualityComparer GetComparer(OrderedDictionary collection) => collection.Comparer; - protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedDefaultComparerType; + protected override Type ExpectedInternalComparerTypeBeforeCollisionThreshold => nonRandomizedOrdinalComparerType; protected override IEqualityComparer ExpectedPublicComparerBeforeCollisionThreshold => EqualityComparer.Default; protected override Type ExpectedInternalComparerTypeAfterCollisionThreshold => EqualityComparer.Default.GetType(); } @@ -242,7 +242,6 @@ public class InternalHashCodeTests_OrderedDictionary_LinguisticComparer : Intern public abstract class InternalHashCodeTests { - protected static Type nonRandomizedDefaultComparerType = typeof(object).Assembly.GetType("System.Collections.Generic.NonRandomizedStringEqualityComparer+DefaultComparer", throwOnError: true); protected static Type nonRandomizedOrdinalComparerType = typeof(object).Assembly.GetType("System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalComparer", throwOnError: true); protected static Type nonRandomizedOrdinalIgnoreCaseComparerType = typeof(object).Assembly.GetType("System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer", throwOnError: true); protected static Type randomizedOrdinalComparerType = typeof(object).Assembly.GetType("System.Collections.Generic.RandomizedStringEqualityComparer+OrdinalComparer", throwOnError: true); diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs index aedbfa953bf88..c1de13d65dea5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs @@ -262,4 +262,47 @@ public override bool Equals([NotNullWhen(true)] object? obj) => public override int GetHashCode() => GetType().GetHashCode(); } + + // This class exists to be EqualityComparer.Default. It can't just use the GenericEqualityComparer, + // as it needs to also implement IAlternateEqualityComparer, string>, and it can't be + // StringComparer.Ordinal, as that doesn't derive from the abstract EqualityComparer. + [Serializable] + internal sealed partial class StringEqualityComparer : + EqualityComparer, + IAlternateEqualityComparer, string>, + ISerializable + { + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + // This type was introduced after BinaryFormatter was deprecated. BinaryFormatter serializes + // types from System.Private.CoreLib without an assembly name, and then on deserialization assumes + // "mscorlib". To support such roundtripping, this type would need to be public and type-forwarded from + // the mscorlib shim. Instead, we serialize this instead as a GenericEqualityComparer. The + // resulting behavior is the same, except it won't implement IAlternateEqualityComparer, and so functionality + // that relies on that interface (which was also introduced after BinaryFormatter was deprecated) won't work. + info.SetType(typeof(GenericEqualityComparer)); + } + + public override bool Equals(string? x, string? y) => string.Equals(x, y); + + public override int GetHashCode([DisallowNull] string obj) => obj?.GetHashCode() ?? 0; + + public bool Equals(ReadOnlySpan span, string target) + { + // See explanation in OrdinalComparer.Equals. + if (span.IsEmpty && target is null) + { + return false; + } + + return span.SequenceEqual(target); + } + + public int GetHashCode(ReadOnlySpan span) => string.GetHashCode(span); + + public string Create(ReadOnlySpan span) => span.ToString(); + + public override bool Equals(object? obj) => obj is StringEqualityComparer; + public override int GetHashCode() => GetType().GetHashCode(); + } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs index b3a5d8d801313..9bc99f7ef4c82 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs @@ -20,7 +20,7 @@ public class NonRandomizedStringEqualityComparer : IEqualityComparer, I // that was passed in to the ctor. The caller chooses one of these singletons so that the // GetUnderlyingEqualityComparer method can return the correct value. - private static readonly NonRandomizedStringEqualityComparer WrappedAroundDefaultComparer = new DefaultComparer(EqualityComparer.Default); + private static readonly NonRandomizedStringEqualityComparer WrappedAroundDefaultComparer = new OrdinalComparer(EqualityComparer.Default); private static readonly NonRandomizedStringEqualityComparer WrappedAroundStringComparerOrdinal = new OrdinalComparer(StringComparer.Ordinal); private static readonly NonRandomizedStringEqualityComparer WrappedAroundStringComparerOrdinalIgnoreCase = new OrdinalIgnoreCaseComparer(StringComparer.OrdinalIgnoreCase); @@ -74,27 +74,6 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex info.SetType(typeof(GenericEqualityComparer)); } - // TODO https://github.com/dotnet/runtime/issues/102906: - // This custom class exists because EqualityComparer.Default doesn't implement IAlternateEqualityComparer, string>. - // If OrdinalComparer were used, then a dictionary created with a null/Default comparer would be using a comparer that does - // implement IAlternateEqualityComparer, string>, but only until it hits a collision and switches to the randomized comparer. - // Once EqualityComparer.Default implements IAlternateEqualityComparer, string>, we can remove this class, and change - // WrappedAroundDefaultComparer to be an instance of OrdinalComparer. - private sealed class DefaultComparer : NonRandomizedStringEqualityComparer - { - internal DefaultComparer(IEqualityComparer wrappedComparer) : base(wrappedComparer) - { - } - - public override bool Equals(string? x, string? y) => string.Equals(x, y); - - public override int GetHashCode(string? obj) - { - Debug.Assert(obj != null, "This implementation is only called from first-party collection types that guarantee non-null parameters."); - return obj.GetNonRandomizedHashCode(); - } - } - private sealed class OrdinalComparer : NonRandomizedStringEqualityComparer, IAlternateEqualityComparer, string?> { internal OrdinalComparer(IEqualityComparer wrappedComparer) : base(wrappedComparer) diff --git a/src/libraries/System.Private.CoreLib/src/System/StringComparer.cs b/src/libraries/System.Private.CoreLib/src/System/StringComparer.cs index d6f0f02c0f025..cb99f1ab72f60 100644 --- a/src/libraries/System.Private.CoreLib/src/System/StringComparer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/StringComparer.cs @@ -88,7 +88,7 @@ public static bool IsWellKnownOrdinalComparer(IEqualityComparer? compar { case StringComparer stringComparer: return stringComparer.IsWellKnownOrdinalComparerCore(out ignoreCase); - case GenericEqualityComparer: + case StringEqualityComparer: // special-case EqualityComparer.Default, which is Ordinal-equivalent ignoreCase = false; return true; diff --git a/src/mono/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.Mono.cs index 6d6bb681e1a9e..6ea693484a8e7 100644 --- a/src/mono/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.Mono.cs @@ -37,8 +37,7 @@ private static EqualityComparer CreateComparer() if (t == typeof(string)) { - // Specialize for string, as EqualityComparer.Default is on the startup path - return (EqualityComparer)(object)(new GenericEqualityComparer()); + return (EqualityComparer)(object)new StringEqualityComparer(); } if (typeof(IEquatable).IsAssignableFrom(t)) From 8653afedd987368dbe41ebe5716d9ee88bf139a9 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 30 Jun 2024 20:47:07 -0400 Subject: [PATCH 2/5] Fixes and feedback --- .../TypeSystem/IL/Stubs/ComparerIntrinsics.cs | 7 ++++- src/coreclr/vm/jitinterface.cpp | 3 +- .../Collections/Generic/EqualityComparer.cs | 26 +++++++++++++----- .../src/System/StringComparer.cs | 6 ++-- .../Legacy/BinaryFormatterTestData.cs | 2 +- .../Legacy/BinaryFormatterTests.cs | 2 +- .../tests/TestData.resources | Bin 8046 -> 8184 bytes .../tests/BinaryFormatterTestData.cs | 2 +- .../tests/BinaryFormatterTests.cs | 2 +- 9 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/ComparerIntrinsics.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/ComparerIntrinsics.cs index 8835a98168582..e62d825af5a11 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/ComparerIntrinsics.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/ComparerIntrinsics.cs @@ -75,7 +75,7 @@ private static MethodIL EmitComparerAndEqualityComparerCreateCommon(MethodDesc m /// Gets the comparer type that is suitable to compare instances of /// or null if such comparer cannot be determined at compile time. /// - private static InstantiatedType GetComparerForType(TypeDesc type, string flavor, string interfaceName) + private static TypeDesc GetComparerForType(TypeDesc type, string flavor, string interfaceName) { TypeSystemContext context = type.Context; @@ -92,6 +92,11 @@ private static InstantiatedType GetComparerForType(TypeDesc type, string flavor, .MakeInstantiatedType(type.Instantiation[0]); } + if (type.IsString && flavor == "EqualityComparer") + { + return context.SystemModule.GetKnownType("System.Collections.Generic", "StringEqualityComparer"); + } + if (type.IsEnum) { // Enums have a specialized comparer that avoids boxing diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index a33069ca969a1..d30bfbd6dcad0 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -8922,8 +8922,7 @@ CORINFO_CLASS_HANDLE CEEInfo::getDefaultEqualityComparerClassHelper(CORINFO_CLAS // string if (elemTypeHnd.IsString()) { - TypeHandle resultTh = ((TypeHandle)CoreLibBinder::GetClass(CLASS__STRING_EQUALITYCOMPARER)).Instantiate(inst); - return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable()); + return CORINFO_CLASS_HANDLE(CoreLibBinder::GetClass(CLASS__STRING_EQUALITYCOMPARER)); } // Nullable diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs index c1de13d65dea5..43b2612505680 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs @@ -265,7 +265,7 @@ public override int GetHashCode() => // This class exists to be EqualityComparer.Default. It can't just use the GenericEqualityComparer, // as it needs to also implement IAlternateEqualityComparer, string>, and it can't be - // StringComparer.Ordinal, as that doesn't derive from the abstract EqualityComparer. + // StringComparer.Ordinal, as that doesn't derive from the required abstract EqualityComparer base class. [Serializable] internal sealed partial class StringEqualityComparer : EqualityComparer, @@ -274,12 +274,24 @@ internal sealed partial class StringEqualityComparer : { void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { - // This type was introduced after BinaryFormatter was deprecated. BinaryFormatter serializes - // types from System.Private.CoreLib without an assembly name, and then on deserialization assumes - // "mscorlib". To support such roundtripping, this type would need to be public and type-forwarded from - // the mscorlib shim. Instead, we serialize this instead as a GenericEqualityComparer. The - // resulting behavior is the same, except it won't implement IAlternateEqualityComparer, and so functionality - // that relies on that interface (which was also introduced after BinaryFormatter was deprecated) won't work. + // This type is added as an internal implementation detail in .NET 9. Even though as of .NET 9 BinaryFormatter has been + // deprecated, for back compat we still need to support serializing this type, especially when EqualityComparer.Default + // is used as part of a collection, like Dictionary. + // + // BinaryFormatter treats types in the core library as being special, in that it doesn't include the assembly as part of the + // serialized data, and then on deserialization it assumes the type is in mscorlib. We could make the type public and type forward + // it from the mscorlib shim, which would enable roundtripping on .NET 9+, but because this type doesn't exist downlevel, it would + // break serializing on .NET 9+ and deserializing downlevel. Therefore, we need to serialize as something that exists downlevel. + + // We could serialize as OrdinalComparer, which does exist downlevel, and which has the nice property that it also implements + // IAlternateEqualityComparer, string>, which means serializing an instance on .NET 9+ and deserializing it + // on .NET 9+ would continue to support span-based lookups. However, OrdinalComparer is not an EqualityComparer, which + // means the type's public ancestry would not be retained, which could lead to strange casting-related errors, including downlevel. + + // Instead, we can serialize as a GenericEqualityComparer. This exists downlevel and also derives from EqualityComparer, + // but doesn't implement IAlternateEqualityComparer, string>. This means that upon deserializing on .NET 9+, + // the comparer loses its ability to handle span-based lookups. As BinaryFormatter is deprecated on .NET 9+, this is a readonable tradeoff. + info.SetType(typeof(GenericEqualityComparer)); } diff --git a/src/libraries/System.Private.CoreLib/src/System/StringComparer.cs b/src/libraries/System.Private.CoreLib/src/System/StringComparer.cs index cb99f1ab72f60..287ede3cd97c2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/StringComparer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/StringComparer.cs @@ -88,10 +88,12 @@ public static bool IsWellKnownOrdinalComparer(IEqualityComparer? compar { case StringComparer stringComparer: return stringComparer.IsWellKnownOrdinalComparerCore(out ignoreCase); - case StringEqualityComparer: - // special-case EqualityComparer.Default, which is Ordinal-equivalent + + case StringEqualityComparer: // EqualityComparer.Default + case GenericEqualityComparer: // EqualityComparer.Default serialized ignoreCase = false; return true; + default: // unknown comparer ignoreCase = default; diff --git a/src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTestData.cs b/src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTestData.cs index 286f0277f427d..93da58616cc04 100644 --- a/src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTestData.cs +++ b/src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTestData.cs @@ -78,6 +78,7 @@ public partial class BinaryFormatterTests yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAANUBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5VSW50NjRFbnVtLCBTeXN0ZW0uUmVzb3VyY2VzLkV4dGVuc2lvbnMuQmluYXJ5Rm9ybWF0LlRlc3RzLCBWZXJzaW9uPTkuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Y2M3YjEzZmZjZDJkZGQ1MV1dAAAAAAs=", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAANUBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5VSW50NjRFbnVtLCBTeXN0ZW0uUmVzb3VyY2VzLkV4dGVuc2lvbnMuQmluYXJ5Rm9ybWF0LlRlc3RzLCBWZXJzaW9uPTkuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Y2M3YjEzZmZjZDJkZGQ1MV1dAAAAAAs=", TargetFrameworkMoniker.netfx461) }); yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAANQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5TQnl0ZUVudW0sIFN5c3RlbS5SZXNvdXJjZXMuRXh0ZW5zaW9ucy5CaW5hcnlGb3JtYXQuVGVzdHMsIFZlcnNpb249OS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1jYzdiMTNmZmNkMmRkZDUxXV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAANQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5TQnl0ZUVudW0sIFN5c3RlbS5SZXNvdXJjZXMuRXh0ZW5zaW9ucy5CaW5hcnlGb3JtYXQuVGVzdHMsIFZlcnNpb249OS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1jYzdiMTNmZmNkMmRkZDUxXV0AAAAACw==", TargetFrameworkMoniker.netfx461) }); yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAANQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5JbnQxNkVudW0sIFN5c3RlbS5SZXNvdXJjZXMuRXh0ZW5zaW9ucy5CaW5hcnlGb3JtYXQuVGVzdHMsIFZlcnNpb249OS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1jYzdiMTNmZmNkMmRkZDUxXV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAANQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5JbnQxNkVudW0sIFN5c3RlbS5SZXNvdXJjZXMuRXh0ZW5zaW9ucy5CaW5hcnlGb3JtYXQuVGVzdHMsIFZlcnNpb249OS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1jYzdiMTNmZmNkMmRkZDUxXV0AAAAACw==", TargetFrameworkMoniker.netfx461) }); + yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netfx461) }); } /// @@ -667,7 +668,6 @@ public partial class BinaryFormatterTests // Nullable equality comparer roundtrips as opposed to other equality comparers which serialize to ObjectEqualityComparer yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDMyLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDMyLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netfx461) }); yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDY0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDY0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netfx461) }); - yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netfx461) }); yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uT2JqZWN0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uT2JqZWN0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netfx461) }); yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5JbnQzMiwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5JbnQzMiwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netfx461) }); yield return (EqualityComparer.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJMBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5Eb3VibGUsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAAAAAAs=", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJMBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5Eb3VibGUsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAAAAAAs=", TargetFrameworkMoniker.netfx461) }); diff --git a/src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTests.cs b/src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTests.cs index 8f99d4437ba36..fbd0544153a76 100644 --- a/src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTests.cs +++ b/src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTests.cs @@ -182,7 +182,7 @@ private static void ValidateEqualityComparer(object obj) { Type objType = obj.GetType(); Assert.True(objType.IsGenericType, $"Type `{objType.FullName}` must be generic."); - Assert.Equal("System.Collections.Generic.ObjectEqualityComparer`1", objType.GetGenericTypeDefinition().FullName); + Assert.True(objType.GetGenericTypeDefinition().FullName is "System.Collections.Generic.ObjectEqualityComparer`1" or "System.Collections.Generic.GenericEqualityComparer`1"); Assert.Equal(obj.GetType().GetGenericArguments()[0], objType.GetGenericArguments()[0]); } diff --git a/src/libraries/System.Resources.Extensions/tests/TestData.resources b/src/libraries/System.Resources.Extensions/tests/TestData.resources index 10efbf9fd4044cc3c3ef12f52910572ae9d02c84..0f45dd390ac64a04df7332ad4793b5a283812b6b 100644 GIT binary patch delta 734 zcmcgpO=}ZT6g`tCwyL|%WdJuoTkz{>3U^KN3mLw&sv5awjkvExVO^1)d9={OTk3Yj zc_*L|NqhLnnqAaBu`Y3gv-a5Vx&@e#zKt2Yw}FvC!ItTXa@F3iedQPXp!S0$$2Zar zP+8!lDr--8(J3B2r2xMpCNInUCb|+kv9TGP70T+0&qV!&BO~tx8kndbrP5s^mTt$O_}vinf2m4OQ9sSU@L~J$~7G%5rv(w#*aRwZw|aoVQAMNaw!% zw4OxLe8A^=k3NdVEwq$H%G>C) zax1@^FZ{xU5jD*Qi!~!HnQ@~v7X~$LQ*@EmkUx#-aYH_EK#$wUwg2aE?B5-(eDjyg EpS^LpWB>pF delta 710 zcmcIh&ubH55PjQbH(z$YO*dJZ+PLJ0f`=kXdJw!QTIfZsE$Bg!hFG^z8&gTLLJNxi z2jcu29u!aNL8uoGy@(1zE}nX8J$O)iiElR%#FG~fJM6soX1+HwKThwRde*l6cPT*M zz&avaM++fdDS2d9r`{^S3(+Qi2X%6{_`j{>rCl{L(zlD=d zsd&z0`7?n7T{hcf_55GW`&vAA+T5Y0Buj2-Y*)*Id))t)2aOVE zJ*4H0TkEvtnHB$a6DzlKbfMl!%dx$v_p5sJQhZo;KxS{+zKd+xexFX@aS> vaHD4N(y;C|T2WXtz7Gb2eSTJ46w|X7k1(QVoioP&bv5(vu3kKt+El*)LO!4n diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTestData.cs b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTestData.cs index d3e06e69b4392..c2d95437fded5 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTestData.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTestData.cs @@ -93,6 +93,7 @@ public static IEnumerable SerializableEqualityComparers_MemberData() yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAOEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMuVUludDY0RW51bSwgU3lzdGVtLlJ1bnRpbWUuU2VyaWFsaXphdGlvbi5Gb3JtYXR0ZXJzLlRlc3RzLCBWZXJzaW9uPTQuMC4zLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49OWQ3N2NjN2FkMzliNjhlYl1dAAAAAAs=", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAOEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMuVUludDY0RW51bSwgU3lzdGVtLlJ1bnRpbWUuU2VyaWFsaXphdGlvbi5Gb3JtYXR0ZXJzLlRlc3RzLCBWZXJzaW9uPTQuMC4zLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49OWQ3N2NjN2FkMzliNjhlYl1dAAAAAAs=", TargetFrameworkMoniker.netfx461) } }; yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAOABU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMuU0J5dGVFbnVtLCBTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMsIFZlcnNpb249NC4wLjMuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj05ZDc3Y2M3YWQzOWI2OGViXV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAOABU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMuU0J5dGVFbnVtLCBTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMsIFZlcnNpb249NC4wLjMuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj05ZDc3Y2M3YWQzOWI2OGViXV0AAAAACw==", TargetFrameworkMoniker.netfx461) } }; yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAOABU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMuSW50MTZFbnVtLCBTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMsIFZlcnNpb249NC4wLjMuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj05ZDc3Y2M3YWQzOWI2OGViXV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAOABU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMuSW50MTZFbnVtLCBTeXN0ZW0uUnVudGltZS5TZXJpYWxpemF0aW9uLkZvcm1hdHRlcnMuVGVzdHMsIFZlcnNpb249NC4wLjMuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj05ZDc3Y2M3YWQzOWI2OGViXV0AAAAACw==", TargetFrameworkMoniker.netfx461) } }; + yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netfx461) } }; } /// @@ -820,7 +821,6 @@ public static IEnumerable SerializableObjects() // Nullable equality comparer roundtrips as opposed to other equality comparers which serialize to ObjectEqualityComparer yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDMyLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDMyLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netfx461) } }; yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDY0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDY0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netfx461) } }; - yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netfx461) } }; yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uT2JqZWN0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uT2JqZWN0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netfx461) } }; yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5JbnQzMiwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5JbnQzMiwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netfx461) } }; yield return new object[] { EqualityComparer.Default, new TypeSerializableValue[] { new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJMBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5Eb3VibGUsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAAAAAAs=", TargetFrameworkMoniker.netcoreapp20), new TypeSerializableValue("AAEAAAD/////AQAAAAAAAAAEAQAAAJMBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5Eb3VibGUsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAAAAAAs=", TargetFrameworkMoniker.netfx461) } }; diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs index 82e7f045e384d..4587f3e47b1fa 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs @@ -540,7 +540,7 @@ private static void ValidateEqualityComparer(object obj) { Type objType = obj.GetType(); Assert.True(objType.IsGenericType, $"Type `{objType.FullName}` must be generic."); - Assert.Equal("System.Collections.Generic.ObjectEqualityComparer`1", objType.GetGenericTypeDefinition().FullName); + Assert.True(objType.GetGenericTypeDefinition().FullName is "System.Collections.Generic.ObjectEqualityComparer`1" or "System.Collections.Generic.GenericEqualityComparer`1"); Assert.Equal(obj.GetType().GetGenericArguments()[0], objType.GetGenericArguments()[0]); } From c0f74cfec459d91287bb6223b86755205a28666a Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 30 Jun 2024 23:34:20 -0400 Subject: [PATCH 3/5] Fix another test --- .../System.Formats.Nrbf/tests/ReadAnythingTests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Formats.Nrbf/tests/ReadAnythingTests.cs b/src/libraries/System.Formats.Nrbf/tests/ReadAnythingTests.cs index fd84071fac8e0..66cbab818dbba 100644 --- a/src/libraries/System.Formats.Nrbf/tests/ReadAnythingTests.cs +++ b/src/libraries/System.Formats.Nrbf/tests/ReadAnythingTests.cs @@ -13,7 +13,7 @@ public class ReadAnythingTests : ReadTests [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Fails with ValueTuple is not marked as serializable, but only in this repo")] public void UserCanReadAnyValidInputAndCheckTypesUsingStronglyTypedTypeInstances() { - Dictionary input = new() + Dictionary input = new(new CustomStringComparer()) { { "exception", new Exception("test") }, { "struct", new ValueTuple(true, 123) }, @@ -269,6 +269,13 @@ static void VerifyDictionary(ClassRecord record) } } + [Serializable] + public class CustomStringComparer : IEqualityComparer + { + public bool Equals(string? x, string? y) => x == y; + public int GetHashCode(string x) => x?.GetHashCode() ?? 0; + } + [Serializable] public class NonSystemPoint : IComparable, IEquatable { From bf382ecc2aeb0888df9d1afba9ba634ee9a985fd Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 1 Jul 2024 09:52:40 -0400 Subject: [PATCH 4/5] Fix another test --- src/tests/JIT/opt/Devirtualization/Comparer_get_Default.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/JIT/opt/Devirtualization/Comparer_get_Default.cs b/src/tests/JIT/opt/Devirtualization/Comparer_get_Default.cs index 5544af8529d6f..9e49c4ac69efc 100644 --- a/src/tests/JIT/opt/Devirtualization/Comparer_get_Default.cs +++ b/src/tests/JIT/opt/Devirtualization/Comparer_get_Default.cs @@ -387,7 +387,7 @@ private static void GetTypeTests() AssertEquals("System.Collections.Generic.GenericEqualityComparer`1[System.Byte]", EqualityComparer.Default.GetType().ToString()); AssertEquals("System.Collections.Generic.GenericEqualityComparer`1[System.Int32]", EqualityComparer.Default.GetType().ToString()); - AssertEquals("System.Collections.Generic.GenericEqualityComparer`1[System.String]", EqualityComparer.Default.GetType().ToString()); + AssertEquals("System.Collections.Generic.StringEqualityComparer", EqualityComparer.Default.GetType().ToString()); AssertEquals("System.Collections.Generic.GenericEqualityComparer`1[System.Guid]", EqualityComparer.Default.GetType().ToString()); AssertEquals("System.Collections.Generic.EnumEqualityComparer`1[System.Runtime.CompilerServices.MethodImplOptions]", EqualityComparer.Default.GetType().ToString()); AssertEquals("System.Collections.Generic.EnumEqualityComparer`1[CharEnum]", EqualityComparer.Default.GetType().ToString()); From d13131eeedaaf1267d1490b060aecb28eea419e2 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 1 Jul 2024 09:56:16 -0400 Subject: [PATCH 5/5] Address test feedback --- .../ConcurrentDictionaryTests.cs | 33 ++++++++----------- .../Generic/CollectionExtensionsTests.cs | 33 ++++++++----------- 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs index 3302686ac06b6..ce05af556da9c 100644 --- a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs @@ -1105,28 +1105,23 @@ public void GetAlternateLookup_FailsWhenIncompatible() Assert.False(dictionary.TryGetAlternateLookup(out _)); } + public static IEnumerable Dictionary_GetAlternateLookup_OperationsMatchUnderlyingDictionary_MemberData() + { + yield return new object[] { EqualityComparer.Default }; + yield return new object[] { StringComparer.Ordinal }; + yield return new object[] { StringComparer.OrdinalIgnoreCase }; + yield return new object[] { StringComparer.InvariantCulture }; + yield return new object[] { StringComparer.InvariantCultureIgnoreCase }; + yield return new object[] { StringComparer.CurrentCulture }; + yield return new object[] { StringComparer.CurrentCultureIgnoreCase }; + } + [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(5)] - [InlineData(6)] - public void GetAlternateLookup_OperationsMatchUnderlyingDictionary(int mode) + [MemberData(nameof(Dictionary_GetAlternateLookup_OperationsMatchUnderlyingDictionary_MemberData))] + public void GetAlternateLookup_OperationsMatchUnderlyingDictionary(IEqualityComparer comparer) { // Test with a variety of comparers to ensure that the alternate lookup is consistent with the underlying dictionary - ConcurrentDictionary dictionary = new(mode switch - { - 0 => EqualityComparer.Default, - 1 => StringComparer.Ordinal, - 2 => StringComparer.OrdinalIgnoreCase, - 3 => StringComparer.InvariantCulture, - 4 => StringComparer.InvariantCultureIgnoreCase, - 5 => StringComparer.CurrentCulture, - 6 => StringComparer.CurrentCultureIgnoreCase, - _ => throw new ArgumentOutOfRangeException(nameof(mode)) - }); + ConcurrentDictionary dictionary = new(comparer); ConcurrentDictionary.AlternateLookup> lookup = dictionary.GetAlternateLookup>(); Assert.Same(dictionary, lookup.Dictionary); Assert.Same(lookup.Dictionary, lookup.Dictionary); diff --git a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs index 00333c6203495..b43f5affdaee5 100644 --- a/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs +++ b/src/libraries/System.Collections/tests/Generic/CollectionExtensionsTests.cs @@ -180,28 +180,23 @@ public void GetAlternateLookup_FailsWhenIncompatible() Assert.False(hashSet.TryGetAlternateLookup(out _)); } + public static IEnumerable Dictionary_GetAlternateLookup_OperationsMatchUnderlyingDictionary_MemberData() + { + yield return new object[] { EqualityComparer.Default }; + yield return new object[] { StringComparer.Ordinal }; + yield return new object[] { StringComparer.OrdinalIgnoreCase }; + yield return new object[] { StringComparer.InvariantCulture }; + yield return new object[] { StringComparer.InvariantCultureIgnoreCase }; + yield return new object[] { StringComparer.CurrentCulture }; + yield return new object[] { StringComparer.CurrentCultureIgnoreCase }; + } + [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(5)] - [InlineData(6)] - public void Dictionary_GetAlternateLookup_OperationsMatchUnderlyingDictionary(int mode) + [MemberData(nameof(Dictionary_GetAlternateLookup_OperationsMatchUnderlyingDictionary_MemberData))] + public void Dictionary_GetAlternateLookup_OperationsMatchUnderlyingDictionary(IEqualityComparer comparer) { // Test with a variety of comparers to ensure that the alternate lookup is consistent with the underlying dictionary - Dictionary dictionary = new(mode switch - { - 0 => EqualityComparer.Default, - 1 => StringComparer.Ordinal, - 2 => StringComparer.OrdinalIgnoreCase, - 3 => StringComparer.InvariantCulture, - 4 => StringComparer.InvariantCultureIgnoreCase, - 5 => StringComparer.CurrentCulture, - 6 => StringComparer.CurrentCultureIgnoreCase, - _ => throw new ArgumentOutOfRangeException(nameof(mode)) - }); + Dictionary dictionary = new(comparer); Dictionary.AlternateLookup> lookup = dictionary.GetAlternateLookup>(); Assert.Same(dictionary, lookup.Dictionary); Assert.Same(lookup.Dictionary, lookup.Dictionary);