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.