Serialization event handler does not have the required signature
ID |
vbnet.correctness.serialization_event_handler_signature |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Serialization |
Language |
VB.NET |
Description
Reports a method marked <OnSerializing>, <OnSerialized>, <OnDeserializing> or
<OnDeserialized> - with or without the Attribute suffix and with or without parentheses -
whose signature is not the one the formatter requires:
Private Sub Name(context As StreamingContext). Four departures are reported: a method that
returns a value, a parameter list that is not exactly one StreamingContext (bare or
namespace-qualified), an accessibility other than Private, and a Shared method. A handler
that matches the required form, with or without ByVal on the parameter, is not reported.
Rationale
These attributes are honoured by convention, not by a contract the compiler can check: the
formatter looks for a method with the exact expected shape, and quietly ignores anything else.
A handler with a wrong signature therefore compiles, reads as though it runs, and does not run.
That matters because the work these callbacks do is the work no other mechanism performs -
deserialization allocates the object without calling a constructor, so field initializers and
constructor logic are both skipped, and <OnDeserialized> is the only place left to rebuild a
cache, resubscribe an event, reopen a handle or restore an invariant across fields. When the
callback silently does not fire, the object arrives half-initialized: a lazily built index is
Nothing, a computed total is zero, a lock or stream field is unset, and the failure surfaces
as a null reference or a wrong answer at the first use, far from the deserialization that
caused it. The wrong-accessibility case is the most misleading of the four, because a Public
handler is callable and unit tests that invoke it directly pass while the formatter path never
touches it. A Shared handler fails differently: there is no instance to initialize, so even
a version the formatter did honour could not do the job. None of these produce a warning at
run time.
The following code illustrates the pattern detected by this rule:
Private cache As Object
' FLAGGED: Serialization event handler does not have the required signature
<OnDeserialized>
Public Sub RebuildCache(ByVal context As StreamingContext)
cache = New Object()
End Sub
Remediation
Give every serialization callback the required signature: Private, Sub rather than
Function, instance rather than Shared, and exactly one StreamingContext parameter -
Private Sub OnDeserializedHandler(context As StreamingContext). Keep the body limited to
restoring the object’s own state, since the order in which callbacks run across an object graph
is not defined and other objects in the graph may not be deserialized yet. If the method needs
to be callable from elsewhere as well, keep the private handler and have it delegate to the
public method.
' Before: Public, so the formatter ignores it and the cache is never rebuilt
<OnDeserialized>
Public Sub RebuildCache(context As StreamingContext)
cache = New Object()
End Sub
' After
<OnDeserialized>
Private Sub RebuildCache(context As StreamingContext)
cache = New Object()
End Sub