Pass Optional Param To Base
ID |
csharp.pass_optional_param_to_base |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Best Practice |
Language |
CSharp |
Tags |
best_practice, default-value, inheritance, parameters |
Description
Reports an override that declares an optional parameter and then calls the method it overrides
without passing that parameter on.
Only a call written as base.Name(…) to the overridden method itself is examined, and only when
the base class is declared in the same file. An override that forwards every parameter, and one
that does not call base at all, are not reported. An override that delegates from more than one
place is reported once per offending call, since each one has to be fixed.
Rationale
An optional parameter always arrives with a value. Either the caller supplied one, or the compiler filled in the declared default at the call site. Nothing distinguishes the two cases inside the method body, and in particular there is no way to pass "the caller said nothing" down to the base implementation.
Omitting the parameter from the base call therefore does not defer to the base default on the
caller’s behalf — it forces it, discarding whatever value this method was handed:
public class Reporter
{
public virtual void Send(string message, int retries = 3) { }
public virtual void Store(string key, int size = 16) { }
}
public class FileReporter : Reporter
{
public override void Send(string message, int retries = 3)
{
base.Send(message); // FLAW - retries is thrown away, the base uses 3
}
public override void Store(string key, int size = 16)
{
base.Store(key, size); // OK - forwarded
}
}
// new FileReporter().Send("hi", 10); the base implementation still retries 3 times
The call site reads as if it asked for ten retries and the override reads as if it honoured the request, so the mismatch shows up only as behaviour that nobody can account for. It also becomes a latent bug: change the base default and every such override changes with it, in a direction the derived class never chose.
Limitations
The base class has to be declared in the same file as the override, because the rule resolves it there and nowhere else. Overriding a base type declared in a different file — the ordinary shape once a class hierarchy spans more than one file, which is most of them — leaves the base declaration unresolved, and the rule says nothing about that override at all, dropped parameter or not.
Remediation
Forward every parameter the override received, by position or by name:
base.Send(message, retries). When the base call is genuinely meant to use a fixed value, pass that
value explicitly — base.Send(message, 1) — so the intent is on the page instead of hidden in an
omission. If the parameter has no meaning for the base implementation at all, it does not belong on
this signature: drop it, and expose the extra behaviour through a separate member.