Field, property & method injection
Field, property, and method injection is how Saneject writes resolved dependencies into components during an editor injection runInjection runOne execution of the injection pipeline for a chosen selection, context walk filter, and active set of targets..
For bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. setup details, see Binding and Scope.
How it works
At a high level, Saneject performs these steps:
- Scans active injection targetsInjection target
Componentwith injected fields, properties or methods. for[Inject]fields and[Inject]methods. - Also scans nested
[Serializable]class instances inside those components. - For each injection siteInjection siteInjected field, property or method., finds a matching bindingBindingInstruction declared in a
Scopethat tells Saneject what to resolve, how to inject it, and where to search. by walking from the nearest scopeScopeMonoBehaviourthat declares bindings for a part of your hierarchy. upward. - Matches on requested type, collection shape (single vs array/list), and optional binding qualifiersBinding qualifierOptional binding restriction declared with
ToID,ToTarget, orToMember. (ID, target type, member name). - Locates dependency objects from the selected bindingBindingInstruction declared in a
Scopethat tells Saneject what to resolve, how to inject it, and where to search. and applies context isolationContext isolationProject setting (UseContextIsolation) that controls whether dependency resolution can cross context boundaries. rules. - Injects values in this order: global registrationsGlobal registrationEntry added to
GlobalScopeat runtime, keyed by the component's concrete type and owned by the caller that registered it., fields and auto-properties, then methods.
Important behavior:
- For single-value sites, Saneject assigns the first located candidate.
- For
T[]andList<T>, Saneject assigns all located candidates. - If no match is found, Saneject records missing bindingBindingInstruction declared in a
Scopethat tells Saneject what to resolve, how to inject it, and where to search. or missing dependency errors and injectsnull. suppressMissingErrorsonly suppresses missing bindingBindingInstruction declared in aScopethat tells Saneject what to resolve, how to inject it, and where to search. and missing dependency logs. It does not suppress method invocation exceptions.
Inject attribute options
InjectAttribute supports these forms:
| Syntax | Meaning |
|---|---|
[Inject] |
Type-only matching. |
[Inject("id")] |
Type + ID matching. |
[Inject(suppressMissingErrors: true)] |
Type matching, but suppress missing binding/dependency logs. |
[Inject("id", suppressMissingErrors: true)] |
Type + ID matching, with suppressed missing binding/dependency logs. |
The ID value must match a binding qualifierBinding qualifierOptional binding restriction declared with ToID, ToTarget, or ToMember. declared with ToID(...).
Field injection
Annotate a field with [Inject]. Field injection is persisted through Unity serialization, so injected fields should be serializable.
In practice:
- Concrete fields should be
publicor[SerializeField] private. - Interface fields should use
[SerializeInterface], so Unity can serialize and show them in the inspector. Access modifier does not matter for this case. - Arrays and
List<>require collection bindingsCollection bindingBinding declared as multiple (BindComponents/BindAssets/BindMultiple...) that resolves arrays orList<>injection sites. (BindComponents,BindAssets, or equivalent collection forms).
using System.Collections.Generic;
using Plugins.Saneject.Runtime.Attributes;
using UnityEngine;
using UnityEngine.UI;
public partial class HUDView : MonoBehaviour
{
// Concrete component dependency.
[Inject, SerializeField]
private Camera mainCamera;
// Same type can be disambiguated by ID.
[Inject("scoreText"), SerializeField]
private Text scoreText;
// Interface dependency.
[Inject, SerializeInterface]
private IScoreService scoreService;
// Interface collection dependency.
[Inject, SerializeInterface]
private List<IEnemyObservable> enemyObservables;
// Collection dependencies.
[Inject, SerializeField]
private AudioClip[] hitSfx;
[Inject, SerializeField]
private List<AudioClip> uiSfx;
// Optional dependency. Missing logs are suppressed.
[Inject(suppressMissingErrors: true), SerializeField]
private Text optionalDebugText;
}
using Plugins.Saneject.Runtime.Scopes;
public class HUDScope : Scope
{
protected override void DeclareBindings()
{
BindComponent<Camera>()
.ToMember("mainCamera")
.FromAnywhere();
BindComponent<Text>()
.ToID("scoreText")
.FromScopeDescendants(includeSelf: true);
BindComponent<IScoreService, ScoreService>()
.FromRuntimeProxy()
.FromGlobalScope();
BindComponents<IEnemyObservable>()
.FromAnywhere();
BindAssets<AudioClip>()
.ToMember("hitSfx")
.FromFolder("Assets/Game/Audio/Sfx/Hit");
BindAssets<AudioClip>()
.ToMember("uiSfx")
.FromFolder("Assets/Game/Audio/Sfx/UI");
}
}
Property injection
InjectAttribute targets fields and methods, but not normal properties.
For properties, use auto-properties with a field target:
[field: Inject][field: SerializeField]for concrete types[field: SerializeInterface]for interface types
using System.Collections.Generic;
using Plugins.Saneject.Runtime.Attributes;
using UnityEngine;
public partial class CombatHud : MonoBehaviour
{
[field: Inject, SerializeField]
public Camera MainCamera { get; private set; }
[field: Inject("playerRoot"), SerializeField]
public Transform PlayerRoot { get; private set; }
[field: Inject, SerializeInterface]
public ICombatService CombatService { get; private set; }
[field: Inject, SerializeInterface]
public IEnemyObservable[] EnemyObservables { get; private set; }
[field: Inject, SerializeField]
public List<AudioClip> UISounds { get; private set; }
}
Notes:
- This works for auto-properties because the compiler generates a backing field that Saneject can inject.
- Binding qualifierBinding qualifierOptional binding restriction declared with
ToID,ToTarget, orToMember.ToMember("MainCamera")matches the logical property name, not the compiler-generated backing field name.
Method injection
Annotate a method with [Inject]. Saneject resolves each parameter from bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. and then invokes the method.
Because this invocation happens at editor-time, method injection is mostly useful for setup/configuration logic. Typical examples include configuring components whose source you cannot modify and add [Inject] to, applying values to built-in Unity components from injected config assets, validating setup, or running custom wiring logic inside your type.
Key rules:
- Qualifiers (
ID,ToTarget,ToMember) are matched against the method as a whole. - Method-level
IDandsuppressMissingErrorssettings apply to all parameters in that method. - Each parameter is resolved by its own requested type and shape.
- Methods are invoked after field and property injection.
- If any parameter is not resolved (
null), the method is not invoked. - Method exceptions are caught and logged. They do not break the overall injection runInjection runOne execution of the injection pipeline for a chosen selection, context walk filter, and active set of targets..
using System.Collections.Generic;
using Plugins.Saneject.Runtime.Attributes;
using UnityEngine;
using UnityEngine.UI;
public class CombatController : MonoBehaviour
{
[Inject("combatInit")]
private void Initialize(
ICombatService combatService,
Camera mainCamera,
IEnemyObservable[] enemyObservables,
List<EnemyController> enemies)
{
// Use injected parameters.
}
[Inject("optionalUI", suppressMissingErrors: true)]
private void TryWireOptionalUI(Text debugText, Button retryButton)
{
if (debugText == null || retryButton == null)
return;
}
}
Example bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. for Initialize:
protected override void DeclareBindings()
{
BindComponent<ICombatService, CombatService>()
.ToID("combatInit")
.ToMember("Initialize")
.FromScopeSelf();
BindComponent<Camera>()
.ToID("combatInit")
.ToMember("Initialize")
.FromAnywhere();
BindComponents<IEnemyObservable, EnemyManager>()
.ToID("combatInit")
.ToMember("Initialize")
.FromScopeDescendants(includeSelf: true);
BindComponents<EnemyController>()
.ToID("combatInit")
.ToMember("Initialize")
.FromScopeDescendants(includeSelf: true);
}
Supported dependency types
Saneject resolves dependencies from component bindingsComponent bindingBinding declared with BindComponent... or BindComponents... that resolves Component instances from transforms, hierarchies, scenes, or explicit instances. and asset bindingsAsset bindingBinding declared with BindAsset... or BindAssets... that resolves UnityEngine.Object assets from project content instead of scene or hierarchy components.. That means injected objects are Unity objects, such as:
UnityEngine.ComponentinstancesUnityEngine.Objectassets, e.g.,ScriptableObject,AudioClip,Texture2D, etc.
Plain C# object (POCO) services are not supported.
For service-like patterns, use runtime proxiesRuntime proxyScriptableObject placeholder asset (RuntimeProxy<TComponent>) injected into interface members at editor time and swapped to the real instance during scope startup. with component implementations. 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. can resolve from global scopeGlobal scopeStatic runtime registry and service locator in Saneject for globally accessible Component instances., loaded scenes, prefabs, or newly created GameObjects, with transient or singleton instance modes. See Runtime proxy for details.
Interface injection notes
Interface injection is supported for fields, auto-properties, and method parameters.
For serialized fields and auto-properties, use [SerializeInterface] so references are visible and persisted in Unity's inspector/serialization workflow. See Serialized interface.