Exported type does not implement the contract it is exported as

ID

vbnet.correctness.implement_exported_interface

Severity

critical

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Api Design

Language

VB.NET

Description

Reports a class exported with an explicit contract type - <Export(GetType(IContract))>, <ExportAttribute>, <InheritedExport> or <InheritedExportAttribute>, with or without a preceding contract name - where the class neither implements nor inherits that contract. Qualified and unqualified spellings are treated as the same contract, so exporting as GetType(IReportRenderer) is satisfied by Implements Acme.Contracts.IReportRenderer and the other way round, and only one of the interfaces in a multi-interface Implements clause has to match. Exporting a type as itself and a bare <Export> with no contract argument are both outside the scope of the rule.

Rationale

The exported contract is a GetType argument, which is to say ordinary data: the compiler has no reason to compare it against the Implements clause, so a class can be exported as an interface it does not provide and still build cleanly and ship. The container is the first thing to notice, and what it does depends on how the contract is imported. A required import fails composition outright, with a CompositionException at start-up that at least names the contract. An ImportMany collection does not fail at all - the part is discovered, the cast to the contract does not succeed, and the renderer, handler or provider is quietly missing from the collection the application iterates. The second outcome is far more expensive to diagnose, because there is no exception, no log entry and no absent file: the plug-in assembly is present on disk, the class is annotated as an export, and the feature just does not appear. The usual cause makes it easy to reintroduce - a plug-in class copied from a sibling, where the Implements clause was updated for the new contract and the <Export> argument was not, or an interface renamed on one side only.

The following code illustrates the pattern detected by this rule:

Namespace Acme.Plugins

    ' FLAGGED: Exported type does not implement the contract it is exported as
    <Export(GetType(IReportRenderer))>
    Public Class CsvReportRenderer
        Implements IReportValidator

        Public Function Validate(rows As Integer) As Boolean Implements IReportValidator.Validate
            Return rows > 0
        End Function

Remediation

Make the export and the type agree, in whichever direction is correct. If the class is supposed to provide the contract, add it to the Implements clause and implement its members. If the export argument is wrong, change the GetType to name the contract the class really provides. When the intent is to export the concrete class for direct import rather than through an abstraction, export it as itself, <Export(GetType(TheClass))>, or use a bare <Export>.

' Before: exported as IReportRenderer, provides IReportValidator
<Export(GetType(IReportRenderer))>
Public Class CsvReportRenderer
    Implements IReportValidator

' After
<Export(GetType(IReportRenderer))>
Public Class CsvReportRenderer
    Implements IReportRenderer

    Public Function Render(rows As Integer) As String Implements IReportRenderer.Render
        Return rows.ToString()
    End Function

Configuration

This detector does not need any configuration.