@@ -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
0 commit comments