Table of Contents

Binding

A bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. is a rule declared in a Scope that tells Saneject:

  • What dependency type should be resolved
  • Where candidates come from
  • Which injection sitesInjection siteInjected field, property or method. may use the bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search.
  • Optional filters that reject candidates

BindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are collected from Scope.DeclareBindings() during editor injection, validated, then used to resolve [Inject] fields, properties and method parameters.

Binding rules

  1. Every bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. must specify a locator strategyLocator strategyThe From... part of a binding that defines where candidates are searched or loaded from. with a From... call.
  2. Type matching is strict:
    • Specifying an interface is optional; concrete-only bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are valid.
    • BindComponent<TConcrete>() and BindAsset<TConcrete>() only match TConcrete injection sitesInjection siteInjected field, property or method..
    • BindComponent<TInterface>() only matches TInterface injection sitesInjection siteInjected field, property or method..
    • BindComponent<TInterface, TConcrete>() and BindAsset<TInterface, TConcrete> only match TInterface injection sites, with a TConcrete object that implements TInterface.
  3. Single and collection bindingsCollection bindingBinding declared as multiple (BindComponents/BindAssets/BindMultiple...) that resolves arrays or List<> injection sites. do not mix:
    • Single bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. (BindComponent, BindAsset) resolve single fields/properties, parameters.
    • Collection bindingsCollection bindingBinding declared as multiple (BindComponents/BindAssets/BindMultiple...) that resolves arrays or List<> injection sites. (BindComponents/BindAssets/BindMultiple...) resolve arrays and List<>.
  4. BindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are local to the declaring scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy.. If the nearest scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy. to an injection siteInjection siteInjected field, property or method. has no matching bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search., Saneject walks up parent scopesScopeMonoBehaviour that declares bindings for a part of your hierarchy. until it finds one.
  5. Invalid bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are excluded from valid resolution and logged.

See Scope for more information

Fluent binding flow

Most bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. follow this general pattern or a combination of these:

BindComponent<IAudioService, AudioManager>()
    .ToID("hud") // Optional qualifier that matches [Inject("hud")]
    .ToTarget<CombatHud>() // Optional qualifier that matches injection targets of type CombatHud
    .ToMember("audioService") // Optional qualifier that matches and methods named "audioService"
    .FromTargetSelf() // Required locator strategy that looks for the AudioManager on the transform of the CombatHud
    .WhereComponent(c => c.isActiveAndEnabled); // Optional filter that only includes enabled components

This resolves candidates in three phases:

  1. Match bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. by binding qualifiersBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. (if any).
  2. Locate candidates with a From... method.
  3. Apply Where... filters (if any).

Binding families

Component bindings

Component bindingsComponent bindingBinding declared with BindComponent... or BindComponents... that resolves Component instances from transforms, hierarchies, scenes, or explicit instances. resolve UnityEngine.Component dependencies from transforms, hierarchy traversal, scene-wide search, or explicit instances.

protected override void DeclareBindings()
{
    // Interface + concrete mapping.
    BindComponent<IAudioService, AudioManager>()
        .FromScopeSelf();

    // Collection binding.
    BindComponents<EnemyController>()
        .FromScopeDescendants(includeSelf: true)
        .WhereComponent(c => c.gameObject.activeInHierarchy);
}

Full component API:

Global component bindings

Global bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are component bindingsComponent bindingBinding declared with BindComponent... or BindComponents... that resolves Component instances from transforms, hierarchies, scenes, or explicit instances. declared with BindGlobal<TComponent>(). The resolved component is serialized into the declaring scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy. at editor time and registered in GlobalScope during that scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy.'s Awake().

protected override void DeclareBindings()
{
    BindGlobal<AudioManager>()
        .FromScopeSelf()
        .WhereGameObject(go => go.activeInHierarchy);
}

Global bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. support locator and filter methods, but not binding qualifiersBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. and not runtime proxyRuntime proxyScriptableObject placeholder asset (RuntimeProxy<TComponent>) injected into interface members at editor time and swapped to the real instance during scope startup. methods.

Full global bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. API:

Asset bindings

Asset bindingsAsset bindingBinding declared with BindAsset... or BindAssets... that resolves UnityEngine.Object assets from project content instead of scene or hierarchy components. resolve UnityEngine.Object assets from Resources, AssetDatabase paths/folders, or explicit instances.

protected override void DeclareBindings()
{
    BindAsset<IGameConfig, GameConfigAsset>()
        .ToID("default")
        .FromResources("Configs/GameConfig");

    BindAssets<AudioClip>()
        .FromFolder("Assets/Game/Audio/Sfx")
        .Where(clip => clip.name.StartsWith("Enemy_"));
}

Full asset API:

Runtime proxy bindings

Runtime proxy bindingsRuntime proxy bindingComponent binding configured with FromRuntimeProxy() that injects a proxy asset at editor time and swaps it for a real runtime instance during scope initialization. are configured from component bindingsComponent bindingBinding declared with BindComponent... or BindComponents... that resolves Component instances from transforms, hierarchies, scenes, or explicit instances. via FromRuntimeProxy(). They inject a proxy asset at editor time, then swap to a real runtime instance in the scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy.'s Awake().

Rules:

  • Must be BindComponent<TInterface, TConcrete>().
  • Must be single-value (not collection).
  • Binding qualifiersBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. are supported.
  • Filters are not supported.
protected override void DeclareBindings()
{
    BindComponent<ICombatService, CombatService>()
        .ToID("combatService")
        .FromRuntimeProxy()
        .FromGlobalScope();
}
protected override void DeclareBindings()
{
    BindComponent<ICombatService, CombatService>()
        .FromRuntimeProxy()
        .FromComponentOnPrefab(combatServicePrefab, dontDestroyOnLoad: true)
        .AsSingleton();
}

Full runtime proxyRuntime proxyScriptableObject placeholder asset (RuntimeProxy<TComponent>) injected into interface members at editor time and swapped to the real instance during scope startup. API:

Binding qualifiers

Binding qualifiersBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. restrict which injection sitesInjection siteInjected field, property or method. (fields, properties, methods) can be resolved from a bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search..

Qualifier Injection site match
ToID("someId") Fields, properties, methods marked with [Inject("someId")]
ToTarget<TTarget>() Fields, properties, methods declared in a TTarget component
ToMember("someMemberName") Fields, properties, methods with name "someMemberName"

Important behavior:

  • Binding qualifiersBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. are additive, so all specified qualifiers must match.
  • If a binding qualifierBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. is not set on the bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search., the bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. matches by TInterface or TConcrete only.
  • Binding qualifiersBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. apply to component, asset, and runtime proxy bindingsRuntime proxy bindingComponent binding configured with FromRuntimeProxy() that injects a proxy asset at editor time and swaps it for a real runtime instance during scope initialization..
  • Binding qualifiersBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. do not apply to global bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search..

Example:

protected override void DeclareBindings()
{
    BindAsset<IGameConfig, GameConfigAsset>()
        .ToID("menu")
        .ToTarget<MainMenuController>()
        .ToMember("config")
        .FromResources("Configs/Menu");
}

Binding filters

Binding filtersBinding filterPredicate declared with Where... methods that removes candidates after location and before injection. run after locator search and before final assignment. A candidate (potentially injected dependency) must pass all filters on the bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search..

Component filter example:

protected override void DeclareBindings()
{
    // First component anywhere in the scene, that is active/enabled and is a descendant of a Transform tagged "GameplayRoot".
    BindComponent<EnemyController>()
        .FromAnywhere()
        .WhereComponent(c => c.isActiveAndEnabled)
        .WhereAnyAncestor(t => t.CompareTag("GameplayRoot"));
}

Asset filter example:

protected override void DeclareBindings()
{
    // All assets in the "Assets/Game/Audio/Music" folder with names starting with "Boss_".
    BindAssets<AudioClip>()
        .FromFolder("Assets/Game/Audio/Music")
        .Where(clip => clip.name.StartsWith("Boss_"));
}
Binding FamilyBinding familyBinding category with its own resolution behavior and API surface: component, global component, asset, or runtime proxy. Filter Support
Component bindingsComponent bindingBinding declared with BindComponent... or BindComponents... that resolves Component instances from transforms, hierarchies, scenes, or explicit instances. Yes
Asset bindingsAsset bindingBinding declared with BindAsset... or BindAssets... that resolves UnityEngine.Object assets from project content instead of scene or hierarchy components. Yes
Global bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. Yes (same filter API as component bindingsComponent bindingBinding declared with BindComponent... or BindComponents... that resolves Component instances from transforms, hierarchies, scenes, or explicit instances.).
Runtime proxy bindingsRuntime proxy bindingComponent binding configured with FromRuntimeProxy() that injects a proxy asset at editor time and swaps it for a real runtime instance during scope initialization. No

If a binding filterBinding filterPredicate declared with Where... methods that removes candidates after location and before injection. throws an exception, Saneject logs a binding filterBinding filterPredicate declared with Where... methods that removes candidates after location and before injection. error for that bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search..

Binding uniqueness

Saneject enforces bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. unique within each Scope. When a bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. is considered duplicate, Saneject logs an error and excludes the duplicate from the injection runInjection runOne execution of the injection pipeline for a chosen selection, context walk filter, and active set of targets..

Duplicate checks use these criteria:

  1. Same scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy..
  2. Same binding familyBinding familyBinding category with its own resolution behavior and API surface: component, global component, asset, or runtime proxy. (ComponentBindingNode, AssetBindingNode, or GlobalComponentBindingNode).
  3. Same primary type:
    • TInterface when present.
    • Otherwise TConcrete.
  4. Same single/collection shape.
  5. Qualifier overlap:
    • If both bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. have no binding qualifiersBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. at all, they conflict.
    • Otherwise, conflict requires full overlap in ToTarget, ToMember, and ToID simultaneously.

Examples:

// Duplicate: same scope, same family, same type, same shape, no qualifiers.
BindComponent<AudioManager>()
    .FromScopeSelf();

BindComponent<AudioManager>()
    .FromScopeParent(); // Invalid duplicate.
// Distinct: different qualifier sets.
BindAsset<IGameConfig, GameConfigAsset>()
    .ToTarget<MainMenuController>()
    .ToMember("config")
    .ToID("menu")
    .FromResources("Configs/Menu");

BindAsset<IGameConfig, GameConfigAsset>()
    .ToTarget<GameplayController>()
    .ToMember("config")
    .ToID("gameplay")
    .FromResources("Configs/Gameplay");

Global bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. have an extra rule: only one global bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. per concrete component type is allowed across active bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. across all scopesScopeMonoBehaviour that declares bindings for a part of your hierarchy.. A second BindGlobal<AudioManager>() is invalid even if declared in another scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy..

Binding validation

While the fluent API prevents most invalid bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search., it's still possible to create invalid bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. that will compile. However, the injection runInjection runOne execution of the injection pipeline for a chosen selection, context walk filter, and active set of targets. has a validation step that catches invalid bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search., excludes them from the run and logs them as errors.

Current validation checks include:

  • Duplicate bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. in the same scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy..
  • Duplicate global bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. by concrete type.
  • Runtime proxy bindingRuntime proxy bindingComponent binding configured with FromRuntimeProxy() that injects a proxy asset at editor time and swaps it for a real runtime instance during scope initialization. constraints:
    • Interface type required.
    • Concrete type required.
    • Collection mode not allowed.
  • Component bindingComponent bindingBinding declared with BindComponent... or BindComponents... that resolves Component instances from transforms, hierarchies, scenes, or explicit instances. concrete type must derive from UnityEngine.Component.
  • Asset bindingAsset bindingBinding declared with BindAsset... or BindAssets... that resolves UnityEngine.Object assets from project content instead of scene or hierarchy components. concrete type must not derive from UnityEngine.Component.
  • Interface type must actually be an interface.
  • Concrete type must implement the declared interface.
  • Locator strategyLocator strategyThe From... part of a binding that defines where candidates are searched or loaded from. must be set.

Examples of invalid but compilable bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search.:

// Invalid: no locator strategy specified.
BindAsset<GameConfigAsset>();
// Invalid: runtime proxy bindings cannot be collection bindings.
BindComponents<ICombatService, CombatService>()
    .FromRuntimeProxy();

Validation runs before dependency resolution, so invalid bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are never used to satisfy [Inject] members.