Implement Exported Interface
ID |
csharp.implement_exported_interface |
Severity |
critical |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
CSharp |
Tags |
api-design, code_smell, composition, dependency-injection |
Description
Reports a class carrying [Export(typeof(IFoo))] that does not implement IFoo. The attribute
declares the contract a composition container publishes the part under, and imports are matched
against that contract — not against the class — so a mismatch is a composition failure waiting to
happen.
Rationale
[Export(typeof(T))] is a promise: whoever imports T may be handed this part. The compiler does
not check the promise, because the attribute argument is just a Type. So a class that exports a
contract it does not satisfy builds cleanly and then fails when the container composes the graph —
either the import is left unsatisfied or the cast into the contract fails. Either way the error
surfaces at startup, in container code, with nothing pointing back at the declaration that caused
it.
The usual cause is drift: the class used to implement the contract, or a copy-paste brought the
wrong typeof along with the attribute.
public interface IGreeter { string Greet(); }
public interface IAuditor { void Audit(string message); }
[Export(typeof(IGreeter))] // FLAW — exports IGreeter, implements IAuditor
public class Farewell : IAuditor
{
public void Audit(string message) { }
}
[Export(typeof(IGreeter))] // OK — implements the contract it exports
public class LoudGreeter : IGreeter
{
public string Greet() => "HELLO";
}
[Export] // OK — no explicit contract, so the part exports its own type
public class SelfExported
{
}
Base types declared in the same file are followed, so a class that inherits the contract from its own base type is not reported. When the contract type, or a base type that might carry it, is declared elsewhere, the rule stays silent rather than guess.
Limitations
The exported contract — the type named by typeof(…) — must be declared in the same file as the
class carrying the [Export] attribute; a contract declared elsewhere is invisible to the rule, and
so is anything a same-file base type might inherit from it. Exported classes are commonly declared
next to the interfaces they implement in small MEF catalogs, but in larger ones the contract
interfaces live in a shared abstractions file or assembly, exactly the layout this rule cannot see
into, so it should be read as covering the smaller, single-file case rather than the composition
graph as a whole.
Remediation
Make the two agree. If the class is meant to serve the contract, add it to the base list and
implement its members. If the typeof names the wrong contract, correct it to the one the class
actually implements. When the part is only ever imported by its own type, drop the argument and use
a bare [Export].