Param Names Match Base Decl
ID |
csharp.param_names_match_base_decl |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
CSharp |
Tags |
api_design, code_smell, inheritance, parameters |
Description
Reports a parameter whose name differs from the name used for the same position by the declaration
being redeclared. Three shapes are checked: an override against its base class member, an explicit
interface implementation against the interface it names, and an ordinary public member against a
same-file interface in the type’s base list.
Only supertypes declared in the same file are compared, so a base class or interface coming from another file or assembly is never reported on. The candidate declaration must also match parameter types position by position — a same-name, same-arity but differently-typed overload is not the member actually being redeclared:
public class Base
{
public virtual bool Equals(Base other) => false;
}
public class Derived : Base
{
public bool Equals(Derived other) => false; // OK - own overload, unrelated to the override below
public override bool Equals(object obj) => false; // OK - real override of object.Equals; not compared
} // against Equals(Base other) just because
// both take exactly one parameter
Rationale
A parameter name is published API. Named arguments bind to the declaration the caller sees, which is the base class or the interface — never the member that ends up running. Renaming a parameter in the implementation therefore leaves two different names for one argument, and only one of them works at a call site:
public interface IChannel
{
void Publish(string topic, int quality);
}
public class Channel : IChannel
{
public void Publish(string subject, int quality) { } // FLAW - the interface calls it 'topic'
public void Close(bool flush) { } // OK
}
// IChannel channel = new Channel();
// channel.Publish(topic: "orders", quality: 1); compiles - names come from IChannel
// channel.Publish(subject: "orders", quality: 1); does not compile
The cost is paid by readers. Somebody tracing the call has to hold two names for the same value; a reviewer comparing the two declarations cannot tell whether the rename was intentional or a slip that changed nothing; and generated documentation reports whichever declaration it was produced from. When the names are near-synonyms it is worse still, because nothing looks wrong until an argument is passed by name.
Limitations
All three shapes — the overridden base member, the named interface of an explicit implementation, and the same-file interface an ordinary public member implements — are resolved only within the file being analysed. A base class or interface declared elsewhere, which is the common case once a hierarchy or a contract is shared across files, leaves the redeclaration unresolved and the parameter names unchecked; the rule only ever compares names when both declarations happen to sit in the same file.
Remediation
Use the inherited name for every parameter. When the inherited name is genuinely poor, rename it in the base class or the interface and let the implementations follow, so the one name that callers can write is also the one the body uses. Where the supertype cannot be changed, keep its names anyway — the implementation is not the place to correct them.