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