Type implements ISerializable and GetObjectData but has no serialization constructor
ID |
vbnet.correctness.implement_serialization_constructors |
Severity |
low |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Serialization |
Language |
VB.NET |
Description
Reports a class that names ISerializable on its Implements clause - bare or
namespace-qualified - and declares GetObjectData, but declares no serialization constructor
Sub New(info As SerializationInfo, context As StreamingContext). The constructor is accepted
with either bare or fully qualified parameter types, at any accessibility, and wherever it sits
in the type body. Types that declare no GetObjectData are left to the rule that reports the
missing interface member.
Rationale
Custom serialization is two matched halves, and the interface only declares one of them.
GetObjectData writes the state; the constructor taking SerializationInfo and
StreamingContext is what reads it back, and because it is a constructor the interface cannot
require it. The compiler therefore accepts a type that can be serialized and never
deserialized. The failure is deferred to run time and to the direction of travel: writing the
object works, so the code that produced the stream looks correct, and the exception -
SerializationException, reporting that the constructor was not found - is raised in whatever
process reads it back. That is typically not the process that has the bug, and often not even
the same deployment: a warm cache, a session hand-off between servers, a queued message, or a
state file written by yesterday’s run. Tests miss it for the same reason, since a test that
exercises GetObjectData alone passes. When the type is a base class the effect is inherited:
every derived type is undeserializable too, and each one needs its own constructor because
constructors are not inherited.
The following code illustrates the pattern detected by this rule:
Namespace Acme.Orders
' FLAGGED: Type implements ISerializable and GetObjectData but has no serialization constructor
<Serializable>
Public Class OrderSnapshot
Implements ISerializable
Private orderId As String
Private total As Decimal
Public Sub New(ByVal orderId As String, ByVal total As Decimal)
Me.orderId = orderId
Me.total = total
End Sub
Remediation
Add a constructor with the signature
Sub New(info As SerializationInfo, context As StreamingContext) that reads back, under the
same names, every value GetObjectData writes, and call
MyBase.New(info, context) first when the base type also implements ISerializable. Make it
Protected on an unsealed type so derived types can chain to it, and Private on a
NotInheritable type. Keep it beside GetObjectData and update both together whenever a field
is added, since nothing checks that the two agree.
' Before: writes its state, cannot be read back
<Serializable>
Public Class OrderSnapshot
Implements ISerializable
Public Sub GetObjectData(info As SerializationInfo, context As StreamingContext) _
Implements ISerializable.GetObjectData
info.AddValue("orderId", orderId)
End Sub
End Class
' After
<Serializable>
Public Class OrderSnapshot
Implements ISerializable
Protected Sub New(info As SerializationInfo, context As StreamingContext)
orderId = info.GetString("orderId")
End Sub
Public Sub GetObjectData(info As SerializationInfo, context As StreamingContext) _
Implements ISerializable.GetObjectData
info.AddValue("orderId", orderId)
End Sub
End Class