> For the complete documentation index, see [llms.txt](https://docs.parsue.io/aceland-unity-packages/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.parsue.io/aceland-unity-packages/core-packages/injection/registration-and-lifetime.md).

# Registration & Lifetime

Before the container can hand you a dependency, something has to **register** it. Registration answers three questions: *what type do I build*, *what contracts does it satisfy*, and *how long does one instance live*. This page covers the registration API, the three lifetimes, and the special shapes the container can resolve automatically.

***

### Where registration happens

You register services inside an **installer** — a small class whose only job is to describe your bindings. The container collects installers and runs them once while building.

{% tabs %}
{% tab title="Global installer" %}

```csharp
using AceLand.Injection;

// Discovered automatically and applied to the global container.
[AutoInstall(order: 0)]
public sealed class ServicesInstaller : IInstaller
{
    public void Install(IContainerBuilder builder)
    {
        builder.Register<IScoreService, ScoreService>(Lifetime.Singleton);
        builder.Register<IAudioService, AudioService>(Lifetime.Singleton);
    }
}
```

{% endtab %}

{% tab title="Scene installer (MonoBehaviour)" %}

```csharp
using AceLand.Injection;

// Drop this on an Injection Scope GameObject in the scene.
public sealed class GameplayInstaller : MonoInstaller
{
    public override void Install(IContainerBuilder builder)
    {
        builder.Register<IEnemyFactory, EnemyFactory>(Lifetime.Scoped);
    }
}
```

{% endtab %}

{% tab title="Asset installer (ScriptableObject)" %}

```csharp
using AceLand.Injection;
using UnityEngine;

// Create as an asset, then reference it from an Injection Scope.
[CreateAssetMenu(menuName = "Game/Config Installer")]
public sealed class ConfigInstaller : ScriptableObjectInstaller
{
    [SerializeField] private GameConfig _config;

    public override void Install(IContainerBuilder builder)
    {
        builder.RegisterInstance(_config);
    }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
`[AutoInstall]` marks a class for automatic discovery. You can also register an installer from another assembly with `[assembly: InjectionInstaller(typeof(MyInstaller), order: 0)]`. Lower `order` runs first. See [For Package Authors](/aceland-unity-packages/core-packages/injection/for-package-authors.md).
{% endhint %}

***

### The three lifetimes

Every registration picks one `Lifetime`:

<table><thead><tr><th width="119.99993896484375">Lifetime</th><th width="336.5999755859375">Meaning</th><th>Use for</th></tr></thead><tbody><tr><td><code>Transient</code></td><td>A <strong>new instance on every resolve</strong>.</td><td>Lightweight, stateless helpers.</td></tr><tr><td><code>Scoped</code></td><td><strong>One instance per resolving scope</strong> — children share their own copy.</td><td>Per-scene / per-request state.</td></tr><tr><td><code>Singleton</code></td><td><strong>One instance</strong> shared by the owning container and all its children.</td><td>Global services, managers.</td></tr></tbody></table>

```csharp
builder.Register<ITracker, Tracker>(Lifetime.Transient);         // fresh each time
builder.Register<ILevelState, LevelState>(Lifetime.Scoped);      // per scope
builder.Register<ISaveService, SaveService>(Lifetime.Singleton); // one shared
```

{% hint style="warning" %}
A **Singleton** must never depend on a **Scoped** service — the singleton outlives the scope and would capture a stale instance. The validator flags this (see [Editor Tools](/aceland-unity-packages/core-packages/injection/editor-tools.md)).
{% endhint %}

***

### Registration methods

All bindings start from one of three `IContainerBuilder` methods (generic overloads shown):

```csharp
// 1. Register a type the container constructs for you.
builder.Register<ScoreService>(Lifetime.Singleton);                // as itself
builder.Register<IScoreService, ScoreService>(Lifetime.Singleton); // as a contract

// 2. Register an object you already have.
builder.RegisterInstance<IClock>(new SystemClock());

// 3. Register a factory delegate.
builder.RegisterFactory<IConnection>(r => new Connection(r.Resolve<Config>()), Lifetime.Scoped);
```

#### Choosing the contracts — `As` / `AsSelf` / `AsImplementedInterfaces`

`Register` returns an `IRegistrationBuilder` you can chain to declare *how* the type is exposed:

```csharp
builder.Register<ScoreService>(Lifetime.Singleton)
       .AsSelf()                   // resolvable as ScoreService
       .As(typeof(IScoreService))  // and as IScoreService
       .AsImplementedInterfaces(); // and every interface it implements
```

#### `RegisterIfMissing` — the polite package binding

Register only when nothing else already provides the contract. Ideal for packages that ship a default but let the game override it:

```csharp
builder.RegisterIfMissing<ILogger, UnityLogger>(Lifetime.Singleton);
```

***

### Keyed registrations

When several implementations share a contract, tag each with `WithId(...)`. Consumers ask for the one they want with `[Inject(Id = ...)]` (see [Injection Points](/aceland-unity-packages/core-packages/injection/injection-points.md)).

```csharp
builder.Register<ICanvas, MainCanvas>(Lifetime.Singleton).WithId("main");
builder.Register<ICanvas, OverlayCanvas>(Lifetime.Singleton).WithId("overlay");
```

Resolve directly by id when you are not using an attribute:

```csharp
var overlay = resolver.Resolve<ICanvas>(id: "overlay");
```

***

### Binding types you cannot annotate

For third-party or external types you cannot add `[Inject]` to, describe the plan explicitly on the registration builder:

```csharp
builder.Register<ExternalService>(Lifetime.Singleton)
       .UsingConstructor(typeof(string), typeof(int)) // pick the ctor
       .WithParameter("endpoint", "https://api.example.com") // by name
       .WithParameter(typeof(int), 8080)                     // by type
       .InjectMember(nameof(ExternalService.Cache), r => r.Resolve<ICache>()) // set a member
       .InvokeMethod(nameof(ExternalService.Warmup))          // call a method after build
       .IgnoreAttributes();                                    // ignore any attributes on it
```

<table data-search="false"><thead><tr><th width="408.5999755859375">Chain call</th><th>Effect</th></tr></thead><tbody><tr><td><code>UsingConstructor(params Type[])</code></td><td>Selects which constructor to use.</td></tr><tr><td><code>WithParameter(name, value / factory)</code></td><td>Supplies a constructor argument by parameter name.</td></tr><tr><td><code>WithParameter(Type, value / factory)</code></td><td>Supplies a constructor argument by parameter type.</td></tr><tr><td><code>InjectMember(name, value / factory, optional)</code></td><td>Sets a field or property after construction.</td></tr><tr><td><code>InvokeMethod(name, params args)</code></td><td>Calls a method once after construction.</td></tr><tr><td><code>IgnoreAttributes()</code></td><td>Ignores <code>[Inject]</code> attributes and uses only the explicit plan.</td></tr><tr><td><code>OnActivated((resolver, instance) => …)</code></td><td>Runs a callback right after the instance is created.</td></tr></tbody></table>

***

### Shapes the container resolves automatically

Beyond a plain `T`, the container understands a few wrapper shapes without extra registration:

<table><thead><tr><th width="190.19989013671875">Requested type</th><th>You get</th></tr></thead><tbody><tr><td><code>IEnumerable&#x3C;T></code>, <br><code>T[]</code>, <br><code>IReadOnlyList&#x3C;T></code></td><td><strong>All</strong> registrations of <code>T</code>, in registration order.</td></tr><tr><td><code>Func&#x3C;T></code></td><td>A lazy factory — each call resolves a fresh <code>T</code> (honouring its lifetime).</td></tr></tbody></table>

```csharp
public sealed class Boss
{
    // Every registered IAttackPhase, collected.
    [Inject] private IReadOnlyList<IAttackPhase> _phases;

    // Deferred creation — nothing is built until you invoke the delegate.
    [Inject] private Func<Projectile> _spawnProjectile;
}
```

***

### Best Practices

* Prefer `Singleton` for services and `Scoped` for per-scene state; reserve `Transient` for cheap, stateless objects.
* Keep installers small and grouped by feature — one installer per subsystem reads best.
* Use `RegisterIfMissing` in packages so consumers can override your defaults.
* Reach for `WithId` only when you genuinely have multiple implementations; otherwise it adds noise.
* Register external types with `UsingConstructor` / `WithParameter` instead of wrapping them.
