Interface Method Callable By Derived
ID |
csharp.interface_method_callable_by_derived |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
CSharp |
Tags |
api-design, code_smell, inheritance, interface |
Description
Reports an explicitly implemented interface member — void IShape.Draw() — on an externally visible,
unsealed class or record that offers no externally visible member of the same name beside it. Sealed
types, structs and types that are internal to their assembly are not reported.
Rationale
An explicit implementation is reachable only through a reference typed as the interface. It carries no access modifier of its own and is not visible as a member of the class, which has two consequences for anyone extending the type.
A derived class cannot reach the inherited behaviour: there is no base.Draw() to call, so
re-implementing the interface member means writing it again from nothing instead of building on what
the base class already does. And when a derived class does re-implement the interface, it takes over
for every caller holding an interface reference without any override keyword at the declaration to
signal that a base behaviour has just been replaced. Both problems land on the consumer of a published
type, who cannot fix them.
using System;
public interface IShape
{
void Draw();
int Sides { get; }
}
public class Square : IShape
{
void IShape.Draw() { } // FLAW, no Draw a derived class can call
int IShape.Sides => 4; // FLAW, no Sides a derived class can call
}
public class Circle : IShape
{
void IShape.Draw() => Draw();
public virtual void Draw() { } // OK, the interface entry point delegates here
public virtual int Sides => 0; // OK
int IShape.Sides => Sides;
}
public sealed class Dot : IShape
{
void IShape.Draw() { } // OK, nothing can derive from a sealed type
int IShape.Sides => 0; // OK
}
Remediation
Add an ordinary public or protected member with the same name, make it virtual if derived types
should be able to change it, and have the explicit implementation forward to it. Callers holding the
interface keep working, and a derived class can now both call and override the behaviour.
When the member really must not be part of the type’s surface, sealing the type states that intent and removes the problem: nothing derives from it, so nothing is cut off.