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.