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

Configuration

This detector does not need any configuration.