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.