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.