PartCreationPolicy is declared on a type that is not exported
ID |
vbnet.correctness.partcreationpolicy_needs_export |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Api Design |
Language |
VB.NET |
Description
Reports a class whose declaration carries <PartCreationPolicy> but no export attribute on the
same declaration. <Export>, <ExportAttribute>, <InheritedExport> and
<InheritedExportAttribute> all count as an export, in either order relative to the policy.
<ExportMetadata> does not: it decorates an export that has to exist independently, so a type
that declares metadata and a creation policy but never exports anything is still reported.
Rationale
A creation policy is a property of a composition part, and a type becomes a part by being
exported. Without an export on the type there is nothing for the policy to describe, so the
container never reads it and the declaration has no effect of any kind - it is not a
misconfiguration that changes behaviour, it is metadata the framework never asks for. What
makes this worth fixing is the impression the surviving attribute leaves. Shared or
NonShared next to a class name is how a reader determines instance lifetime, so the type
appears to be a registered singleton or a per-import instance when in fact nothing composes it
at all. That mismatch is a symptom rather than the disease: an attribute pair usually arrives
together, and finding the policy alone means the export was lost - deleted during a refactor,
moved when discovery was switched to a convention-based registration, or never written on a
class that was copied from a sibling plug-in. The consequence is then the quiet kind: the
import is never satisfied, the feature is absent, and no error identifies the type
responsible.
The following code illustrates the pattern detected by this rule:
Namespace Acme.Plugins
' FLAGGED: PartCreationPolicy is declared on a type that is not exported
<PartCreationPolicy(CreationPolicy.Shared)>
Public Class CsvReportRenderer
Implements IReportRenderer
Public Function Render(rows As Integer) As String Implements IReportRenderer.Render
Return rows.ToString()
End Function
Remediation
Decide which of the two attributes states the truth. If the class is meant to be a part, add
the <Export> that was intended, naming the contract that consumers import - the policy then
applies as written. If it is not a part, remove <PartCreationPolicy> so the declaration stops
implying a lifetime the container does not manage. When the class is composed through a
registration made elsewhere rather than through attributes, express its lifetime there and drop
the attribute. The one case to keep is a type whose exports are declared on its properties or
methods instead of on the class: the type is still a part, so the policy applies as written.
' Before: the policy is inert, nothing composes this type
<PartCreationPolicy(CreationPolicy.Shared)>
Public Class CsvReportRenderer
Implements IReportRenderer
' After
<Export(GetType(IReportRenderer))>
<PartCreationPolicy(CreationPolicy.Shared)>
Public Class CsvReportRenderer
Implements IReportRenderer