No Params On Override

ID

csharp.no_params_on_override

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

CSharp

Tags

api-design, code_smell, inheritance, surprising-behaviour

Description

Reports an override whose last parameter is declared params while the overridden method declares it as a plain array.

The opposite mismatch — an override spelling the parameter as a plain array while the base declares params — is not reported: callers bind against the base declaration, so the expanded call syntax keeps working. A params parameter on a method that overrides nothing is not reported either — that is the ordinary variadic API shape.

Only base classes declared in the same file are compared.

Rationale

params is a call-site convenience, not part of dispatch. The compiler decides whether it may expand a comma-separated argument list into an array by looking at the static type of the receiver, so a params that exists only on the override is unreachable for every polymorphic caller — precisely the callers that overriding exists to serve.

public class Formatter
{
    public virtual string Render(string template, object[] values) => template;
    public virtual string Join(string separator, params string[] parts) => separator;
}

public class HtmlFormatter : Formatter
{
    // FLAW - no caller can use the expanded form through a Formatter reference
    public override string Render(string template, params object[] values) => template;

    // OK - expanded calls keep compiling through the base signature
    public override string Join(string separator, string[] parts) => separator;

    public string Wrap(params object[] values) => "";                 // OK - overrides nothing
}

The modifier reads as an improvement to the API, which is what makes it worth reporting: the author believed they had given callers a friendlier signature, and no caller received one. The mismatch also misleads the next reader of the derived class, who has to check the base declaration to know how the method can actually be called.

Remediation

Remove the params modifier from the override, so the two signatures agree and nothing promises a call form that does not exist. If the variadic form is genuinely wanted, add params to the base declaration instead — that is where callers read it from. When the base declaration cannot be changed, leave the override alone and add a separate variadic method on the derived type that builds the array and forwards.