@@ -265,7 +265,7 @@ public override int GetHashCode() =>
265
265
266
266
// This class exists to be EqualityComparer<string>.Default. It can't just use the GenericEqualityComparer<string>,
267
267
// 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 .
269
269
[ Serializable ]
270
270
internal sealed partial class StringEqualityComparer :
271
271
EqualityComparer < string > ,
@@ -274,12 +274,24 @@ internal sealed partial class StringEqualityComparer :
274
274
{
275
275
void ISerializable . GetObjectData ( SerializationInfo info , StreamingContext context )
276
276
{
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
+
283
295
info . SetType ( typeof ( GenericEqualityComparer < string > ) ) ;
284
296
}
285
297
0 commit comments