Analyzer

SF0001

Flags interfaces that introduce indirection without delivering polymorphism because the solution only has one concrete implementation.

Diagnostic contract

An interface with one implementation is usually ceremony. It makes navigation, DI setup, and onboarding harder without buying substitution power.

SF0001 SimplicityFirst.HalfRule Warning Code fix available
When it fires

Exactly one non-abstract class or struct implements the interface in source.

Rule message

Interface {0} has exactly one non-abstract implementation: {1}. Remove the interface and use the concrete type directly.

Help link target

/analyzers/sf0001/

When it fires

These are the concrete cases to look for in code review and IDE diagnostics.

  • The analyzer looks only at source-defined interfaces, then finds concrete classes or structs that implement them.
  • It reports only when there is exactly one non-abstract implementation. Zero or multiple implementations are left alone.
  • The rule belongs to the HalfRule category because it measures speculative abstraction.

Bad / better example

Use these examples to explain the rule, not just silence it.

Before

public interface IPricingService
{
    decimal Calculate(Order order);
}

public sealed class DefaultPricingService : IPricingService
{
    public decimal Calculate(Order order) => order.Total * 0.95m;
}

public sealed class CheckoutHandler(IPricingService pricingService)
{
    public decimal Quote(Order order) => pricingService.Calculate(order);
}

The interface exists, but the codebase only ever uses DefaultPricingService.

After

public sealed class DefaultPricingService
{
    public decimal Calculate(Order order) => order.Total * 0.95m;
}

public sealed class CheckoutHandler(DefaultPricingService pricingService)
{
    public decimal Quote(Order order) => pricingService.Calculate(order);
}

The direct dependency makes the real shape obvious and removes one layer of indirection.

Code fix

The code fix rewrites interface references to the concrete implementation, removes the interface declaration, and preserves dependent interface members where possible.

  • Use the lightbulb in the IDE when the interface exists only as ceremony.
  • Review the result if the interface participates in a larger hierarchy; the fixer preserves direct dependent members but cannot invent missing abstraction value.
  • This is one of the two diagnostics with an in-repo code fix today.

Source and follow-up links

Use these links when you need to validate behavior against the source or connect the docs back to project tracking.