Open
Description
Original issue: akkadotnet/akka.net#4692
Bug description:
An error occurred while serializing collection of objects implementing IEquatable<T>
with one object that passes equality with another object in the same collection. For the second object that passes equality, ObjectSerializer
is used instead of FromSurrogateSerializer
. This causes the error System.InvalidCastException: Unable to cast object of type [SurrogateType] to type [RealType]
.
Preliminary code assessment:
The bug was actually caused by a few factor:
-
Data marked as
ObjectReferenceSerializer.Manifest
does not honor the proper custom surrogate deserialization flow.- This is caused by a miss-cache of tracked deserialized objects. By definition, objects deserialized from the data stream are supposed to be tracked after they are converted to their original type by the
FromSurrogateSerializer
. This is not what happened in code; the surrogate object is the one that is being cached inside theDeserializeSession
instead.
- This is caused by a miss-cache of tracked deserialized objects. By definition, objects deserialized from the data stream are supposed to be tracked after they are converted to their original type by the
-
The use of
Dictionary<object, int>
in theSerializerSession
class.Dictionary<K, V>
uses the the objectEqual()
equality method to determine whether 2 keys are considered equal. By definition, the serializer should flag an object as a duplicate and mark it as an object reference whenPreserveObjectReference
is set to true in the serializer option. With aDictionary
, this is true when it is using the defaultobject.Equal()
method, as it relies on reference equality between 2 objects to determine if 2 objects are the same, but when used with objects that overrides theobject.Equal()
method, this will no longer be true because the equality will be done by value instead of by reference.- A similar object that passes the equality check will be marked as a reference duplicate, even when it is not the same instance of a type. The object will then be marked with the
ObjectReferenceSerializer.Manifest
manifest, instead of the correctObjectSerializer.ManifestIndex
manifest. - With a proper deserializing session object caching (see 1 above), this would be a silent bug where 2 or more instances of an object that are considered equal by
Dictionary
will be deserialized as references to a single instance of an object. With the malfunctioning object caching, the surrogate class instance were instead retrieved from the session cache, causing theInvalidCastException
in the case ofArraySerializerFactory
because it will fail to assign the surrogate object into the array index.