No New On Shared Part
ID |
csharp.no_new_on_shared_part |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
CSharp |
Tags |
code_smell, composition, dependency-injection, object-lifetime |
Description
Reports new Foo() where Foo is a class annotated [PartCreationPolicy(CreationPolicy.Shared)].
A shared part exists once: the composition container creates a single instance and hands the same
reference to everything that imports it. Constructing one directly produces a second instance the
container knows nothing about.
Rationale
Shared is a statement about identity, not about cost. Code written against a shared part assumes
shared state — a cache every caller reads, a connection every caller reuses, an event subscription
registered exactly once. A direct new breaks all of it at once, and quietly: there is no exception,
just two instances where the design assumed one, drifting apart from each other. Initialization the
container performed on its instance was never performed on this one.
Nothing in the type system objects, because the class has an ordinary public constructor. The constraint lives only in the attribute, which is why it is worth checking.
[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
public class SharedCache
{
public int Hits { get; set; }
}
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class PerCallWorker
{
}
public class Composition
{
public int Count()
{
var cache = new SharedCache(); // FLAW — bypasses the container's single instance
return cache.Hits;
}
public object Worker()
{
return new PerCallWorker(); // OK — NonShared parts are meant to be created per use
}
}
Only classes declared in the same file are examined: for a type declared elsewhere the creation
policy is not visible, and the rule stays silent rather than guess. CreationPolicy.Any is not
reported either, since it leaves the decision to whoever imports the part.
Limitations
The [PartCreationPolicy(CreationPolicy.Shared)] class has to be declared in the same file as the
new expression, since that is the only place the rule looks for the attribute. A shared part is
typically declared once, in its own file, and then constructed — correctly, through the container,
or incorrectly, by mistake — from many other files across the composition graph; the rule can see
none of those other-file call sites, only the rarer case where the errant new happens to live
alongside the declaration it violates.
Remediation
Import the part instead of constructing it: declare an [Import] property or take it as a constructor
parameter marked [ImportingConstructor], and let the container supply the one instance. If the code
genuinely needs its own instance, the part is not shared — change its policy to
CreationPolicy.NonShared, or obtain a separate instance deliberately through the container rather
than around it.