Serialization Event Handler Signature
ID |
csharp.serialization_event_handler_signature |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
api-contract, attributes, reliability, serialization |
Description
Reports a method carrying OnSerializing, OnSerialized, OnDeserializing or OnDeserialized
whose signature is not the one the runtime looks for: returning void, taking exactly one
StreamingContext parameter, on an instance and not generic. Accessibility does not matter — public,
internal and private handlers are all discovered.
Rationale
Serialization callbacks are matched by shape, not by registration. When a type is serialized or restored, the runtime scans it for methods carrying one of the four attributes and having the exact required signature. A method that carries the attribute but differs in any respect is not a candidate, and the runtime does not consider that worth mentioning: no exception, no warning, no log line.
So the failure is a hook that never fires. That hook was written for a reason — reconnect a field marked as not serialized, rebuild a cached value, restore an invariant the constructor normally establishes — and none of it happens. The object graph comes back structurally intact and semantically wrong, and the symptom appears far away from the type that owns the handler.
The requirements each have a reason behind them:
-
Returns
void. There is no caller to receive a result. A return type is a sign the method was written for something else and the attribute was added to it later. -
Exactly one
StreamingContextparameter. That argument carries the reason for the operation, and it is the only thing the runtime is prepared to pass. -
Instance, non-generic. The callback runs against the object being serialized, so there is nothing for a static or generic handler to act on.
Accessibility is not one of the requirements: the runtime discovers these callbacks reflectively
over public and non-public members alike, internal included, so a public or internal handler
with the right shape runs exactly as an equivalent private one would.
Both spellings of every attribute are recognised, qualified or not, since C# allows the Attribute
suffix to be omitted at the use site.
using System;
using System.Runtime.Serialization;
[Serializable]
public class Session
{
private string token;
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context) // OK — accessibility is irrelevant
{
token = "restored";
}
[OnSerializing]
private void Prepare() // FLAW — no StreamingContext parameter
{
token = null;
}
[OnDeserializing]
private void Reset(StreamingContext context) // OK — void, one context parameter
{
token = string.Empty;
}
public void Refresh(StreamingContext context) // OK — no callback attribute at all
{
token = null;
}
}
Remediation
Give the handler the required signature: void Name(StreamingContext context) on an instance,
non-generic method. Drop any return value, remove static and any type parameters. Accessibility
does not need to change — leave it as public, internal or private, whichever fits the type’s
own design.
Once the signature is corrected the handler starts running, which may be the first time its body has ever executed: review it as new code rather than as code that has been working all along.