Skip to content

Commit a6e4488

Browse files
committed
Fixes and feedback
1 parent b627e2c commit a6e4488

File tree

9 files changed

+34
-16
lines changed

9 files changed

+34
-16
lines changed

src/coreclr/tools/Common/TypeSystem/IL/Stubs/ComparerIntrinsics.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ private static MethodIL EmitComparerAndEqualityComparerCreateCommon(MethodDesc m
7575
/// Gets the comparer type that is suitable to compare instances of <paramref name="type"/>
7676
/// or null if such comparer cannot be determined at compile time.
7777
/// </summary>
78-
private static InstantiatedType GetComparerForType(TypeDesc type, string flavor, string interfaceName)
78+
private static TypeDesc GetComparerForType(TypeDesc type, string flavor, string interfaceName)
7979
{
8080
TypeSystemContext context = type.Context;
8181

@@ -92,6 +92,11 @@ private static InstantiatedType GetComparerForType(TypeDesc type, string flavor,
9292
.MakeInstantiatedType(type.Instantiation[0]);
9393
}
9494

95+
if (type.IsString && flavor == "EqualityComparer")
96+
{
97+
return context.SystemModule.GetKnownType("System.Collections.Generic", "StringEqualityComparer");
98+
}
99+
95100
if (type.IsEnum)
96101
{
97102
// Enums have a specialized comparer that avoids boxing

src/coreclr/vm/jitinterface.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8922,8 +8922,7 @@ CORINFO_CLASS_HANDLE CEEInfo::getDefaultEqualityComparerClassHelper(CORINFO_CLAS
89228922
// string
89238923
if (elemTypeHnd.IsString())
89248924
{
8925-
TypeHandle resultTh = ((TypeHandle)CoreLibBinder::GetClass(CLASS__STRING_EQUALITYCOMPARER)).Instantiate(inst);
8926-
return CORINFO_CLASS_HANDLE(resultTh.GetMethodTable());
8925+
return CORINFO_CLASS_HANDLE(CoreLibBinder::GetClass(CLASS__STRING_EQUALITYCOMPARER));
89278926
}
89288927

89298928
// Nullable<T>

src/libraries/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ public override int GetHashCode() =>
265265

266266
// This class exists to be EqualityComparer<string>.Default. It can't just use the GenericEqualityComparer<string>,
267267
// as it needs to also implement IAlternateEqualityComparer<ReadOnlySpan<char>, string>, and it can't be
268-
// StringComparer.Ordinal, as that doesn't derive from the abstract EqualityComparer<T>.
268+
// StringComparer.Ordinal, as that doesn't derive from the required abstract EqualityComparer<T> base class.
269269
[Serializable]
270270
internal sealed partial class StringEqualityComparer :
271271
EqualityComparer<string>,
@@ -274,12 +274,24 @@ internal sealed partial class StringEqualityComparer :
274274
{
275275
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
276276
{
277-
// This type was introduced after BinaryFormatter was deprecated. BinaryFormatter serializes
278-
// types from System.Private.CoreLib without an assembly name, and then on deserialization assumes
279-
// "mscorlib". To support such roundtripping, this type would need to be public and type-forwarded from
280-
// the mscorlib shim. Instead, we serialize this instead as a GenericEqualityComparer<string>. The
281-
// resulting behavior is the same, except it won't implement IAlternateEqualityComparer, and so functionality
282-
// that relies on that interface (which was also introduced after BinaryFormatter was deprecated) won't work.
277+
// This type is added as an internal implementation detail in .NET 9. Even though as of .NET 9 BinaryFormatter has been
278+
// deprecated, for back compat we still need to support serializing this type, especially when EqualityComparer<string>.Default
279+
// is used as part of a collection, like Dictionary<string, TValue>.
280+
//
281+
// BinaryFormatter treats types in the core library as being special, in that it doesn't include the assembly as part of the
282+
// serialized data, and then on deserialization it assumes the type is in mscorlib. We could make the type public and type forward
283+
// it from the mscorlib shim, which would enable roundtripping on .NET 9+, but because this type doesn't exist downlevel, it would
284+
// break serializing on .NET 9+ and deserializing downlevel. Therefore, we need to serialize as something that exists downlevel.
285+
286+
// We could serialize as OrdinalComparer, which does exist downlevel, and which has the nice property that it also implements
287+
// IAlternateEqualityComparer<ReadOnlySpan<char>, string>, which means serializing an instance on .NET 9+ and deserializing it
288+
// on .NET 9+ would continue to support span-based lookups. However, OrdinalComparer is not an EqualityComparer<string>, which
289+
// means the type's public ancestry would not be retained, which could lead to strange casting-related errors, including downlevel.
290+
291+
// Instead, we can serialize as a GenericEqualityComparer<string>. This exists downlevel and also derives from EqualityComparer<string>,
292+
// but doesn't implement IAlternateEqualityComparer<ReadOnlySpan<char>, string>. This means that upon deserializing on .NET 9+,
293+
// the comparer loses its ability to handle span-based lookups. As BinaryFormatter is deprecated on .NET 9+, this is a readonable tradeoff.
294+
283295
info.SetType(typeof(GenericEqualityComparer<string>));
284296
}
285297

src/libraries/System.Private.CoreLib/src/System/StringComparer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,12 @@ public static bool IsWellKnownOrdinalComparer(IEqualityComparer<string?>? compar
8888
{
8989
case StringComparer stringComparer:
9090
return stringComparer.IsWellKnownOrdinalComparerCore(out ignoreCase);
91-
case StringEqualityComparer:
92-
// special-case EqualityComparer<string>.Default, which is Ordinal-equivalent
91+
92+
case StringEqualityComparer: // EqualityComparer<string>.Default
93+
case GenericEqualityComparer<string>: // EqualityComparer<string>.Default serialized
9394
ignoreCase = false;
9495
return true;
96+
9597
default:
9698
// unknown comparer
9799
ignoreCase = default;

src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTestData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public partial class BinaryFormatterTests
7878
yield return (EqualityComparer<UInt64Enum>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAANUBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5VSW50NjRFbnVtLCBTeXN0ZW0uUmVzb3VyY2VzLkV4dGVuc2lvbnMuQmluYXJ5Rm9ybWF0LlRlc3RzLCBWZXJzaW9uPTkuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Y2M3YjEzZmZjZDJkZGQ1MV1dAAAAAAs=", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAANUBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5VSW50NjRFbnVtLCBTeXN0ZW0uUmVzb3VyY2VzLkV4dGVuc2lvbnMuQmluYXJ5Rm9ybWF0LlRlc3RzLCBWZXJzaW9uPTkuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Y2M3YjEzZmZjZDJkZGQ1MV1dAAAAAAs=", TargetFrameworkMoniker.netfx461) });
7979
yield return (EqualityComparer<SByteEnum>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAANQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5TQnl0ZUVudW0sIFN5c3RlbS5SZXNvdXJjZXMuRXh0ZW5zaW9ucy5CaW5hcnlGb3JtYXQuVGVzdHMsIFZlcnNpb249OS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1jYzdiMTNmZmNkMmRkZDUxXV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAANQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5TQnl0ZUVudW0sIFN5c3RlbS5SZXNvdXJjZXMuRXh0ZW5zaW9ucy5CaW5hcnlGb3JtYXQuVGVzdHMsIFZlcnNpb249OS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1jYzdiMTNmZmNkMmRkZDUxXV0AAAAACw==", TargetFrameworkMoniker.netfx461) });
8080
yield return (EqualityComparer<Int16Enum>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAANQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5JbnQxNkVudW0sIFN5c3RlbS5SZXNvdXJjZXMuRXh0ZW5zaW9ucy5CaW5hcnlGb3JtYXQuVGVzdHMsIFZlcnNpb249OS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1jYzdiMTNmZmNkMmRkZDUxXV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAANQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tCaW5hcnlGb3JtYXRUZXN0cy5Gb3JtYXR0ZXJUZXN0cy5JbnQxNkVudW0sIFN5c3RlbS5SZXNvdXJjZXMuRXh0ZW5zaW9ucy5CaW5hcnlGb3JtYXQuVGVzdHMsIFZlcnNpb249OS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1jYzdiMTNmZmNkMmRkZDUxXV0AAAAACw==", TargetFrameworkMoniker.netfx461) });
81+
yield return (EqualityComparer<string>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netfx461) });
8182
}
8283

8384
/// <summary>
@@ -667,7 +668,6 @@ public partial class BinaryFormatterTests
667668
// Nullable equality comparer roundtrips as opposed to other equality comparers which serialize to ObjectEqualityComparer
668669
yield return (EqualityComparer<int>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDMyLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDMyLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netfx461) });
669670
yield return (EqualityComparer<long>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDY0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLkludDY0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netfx461) });
670-
yield return (EqualityComparer<string>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuR2VuZXJpY0VxdWFsaXR5Q29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netfx461) });
671671
yield return (EqualityComparer<object>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uT2JqZWN0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJEBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuT2JqZWN0RXF1YWxpdHlDb21wYXJlcmAxW1tTeXN0ZW0uT2JqZWN0LCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQAAAAAL", TargetFrameworkMoniker.netfx461) });
672672
yield return (EqualityComparer<int?>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5JbnQzMiwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJIBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5JbnQzMiwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0AAAAACw==", TargetFrameworkMoniker.netfx461) });
673673
yield return (EqualityComparer<double?>.Default, new TypeSerializableValue[] { new("AAEAAAD/////AQAAAAAAAAAEAQAAAJMBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5Eb3VibGUsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAAAAAAs=", TargetFrameworkMoniker.netcoreapp20), new("AAEAAAD/////AQAAAAAAAAAEAQAAAJMBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuTnVsbGFibGVFcXVhbGl0eUNvbXBhcmVyYDFbW1N5c3RlbS5Eb3VibGUsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dAAAAAAs=", TargetFrameworkMoniker.netfx461) });

src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/Legacy/BinaryFormatterTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ private static void ValidateEqualityComparer(object obj)
182182
{
183183
Type objType = obj.GetType();
184184
Assert.True(objType.IsGenericType, $"Type `{objType.FullName}` must be generic.");
185-
Assert.Equal("System.Collections.Generic.ObjectEqualityComparer`1", objType.GetGenericTypeDefinition().FullName);
185+
Assert.True(objType.GetGenericTypeDefinition().FullName is "System.Collections.Generic.ObjectEqualityComparer`1" or "System.Collections.Generic.GenericEqualityComparer`1");
186186
Assert.Equal(obj.GetType().GetGenericArguments()[0], objType.GetGenericArguments()[0]);
187187
}
188188

138 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)