No Decreased Inherited Visibility
ID |
csharp.no_decreased_inherited_visibility |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
CSharp |
Tags |
code_smell, inheritance, shadowing, visibility |
Description
Reports a private method of a derived class whose name and parameter count match a public,
protected or protected internal method of its base class that cannot be overridden — one
that is neither virtual nor abstract nor itself an override.
Not reported: a declaration carrying the new modifier, which states the hiding intent
explicitly and is covered by csharp.do_not_hide_base_class_methods; an explicitly implemented
interface member, whose name is qualified and shadows nothing; and the case where the base
method is overridable, which is a dispatch relationship rather than shadowing. Base classes
from other files are not compared.
Rationale
Reducing the visibility of an inherited member is not something C# lets you do. The base method is not virtual, so there is nothing to dispatch and nothing is replaced — the private declaration only shadows the name inside the derived class body. Every other reader of the object keeps calling the base implementation:
public class Repository
{
public void Save(string key) { }
public virtual void Refresh() { }
}
public class CachedRepository : Repository
{
private void Save(string key) { } // FLAW - external callers still reach Repository.Save
private void Refresh() { } // OK - the base method is virtual
private void Evict(string key) { } // OK - no inherited method of that name
}
// new CachedRepository().Save("k"); // Repository.Save runs
// ((Repository)new CachedRepository()).Save("k"); // Repository.Save runs
The declaration reads as if it locked the operation down or replaced it, and it did neither. If the intent was to restrict access, the type hierarchy is the wrong tool; if it was to change behaviour, the base member has to be made overridable first. Either way the code as written leaves two methods of the same name with different bodies, and which one runs depends on where the call is written.
Remediation
If the derived logic is meant to replace the inherited one, make the base method virtual and
declare the derived one as an override with the same access level. If it is a separate
internal helper, rename it so it no longer collides with the inherited name. If the goal was to
keep the inherited operation out of the derived type’s contract, prefer composition — hold the
base type in a field and expose only the members that belong to the new abstraction.