DICS (pronounced dee-see-es) is a staged dependency injection library for C# —
a simplified port of the core ideas in DIstage.
It runs on plain .NET (netstandard2.1) and inside Unity.
Installation
NuGet (plain .NET, netstandard2.1):
The package carries DICS.Generator as a Roslyn analyzer, so [LiftConstructor],
[LiftInitializer], and [LiftFactory] work as soon as the package is referenced.
Unity — add the package by git URL, via Window → Package Manager → Add package
from git URL, or directly in Packages/manifest.json:
{
"dependencies": {
"com.playq.dics": "https://github.com/PlayQ/dics.git",
"com.unity.nuget.newtonsoft-json": "3.2.1"
}
}Unity 2021.3 or newer. com.unity.nuget.newtonsoft-json is a real dependency, not a
convenience: under Unity TraceGen serializes profiles with Newtonsoft, and only
falls back to System.Text.Json off-Unity.
Unity does not pick up the source generator from this repository — DICS.Generator
is restricted to a platform Unity does not compile for in the editor, and no
pre-built analyzer assembly is committed. Under Unity, either lift constructors and
initializers by hand, or build DICS.Generator and drop the DLL into the project as
an asset labelled RoslynAnalyzer.
Why staged DI?
DICS is a three-pass tool:
- Compile-time reflection — a Roslyn source generator wraps constructors and initializers into runtime-inspectable functoids. Optional; you can write functoids by hand.
- Planning — at runtime, DICS computes a project network of operations from your modules and roots. If the graph cannot be wired (missing keys, cycles, ambiguous bindings), planning fails before anything is instantiated.
- Production — DICS executes the plan and returns a
Locatorcontaining every produced instance.
This separation makes DI debuggable: the Plan is a value you can print, diff, or
serialize, and instantiation never reflects against Type beyond typeof and
equality comparison.
DICS is one instance of the Percept-Plan-Execute-Repeat metaprogramming pattern formulated by pshirshov in 2014.
Background reading: the DIstage manual.
Documentation
- Core usage — concepts, modules, factories, lifecycles, the whole non-Unity surface.
- Unity usage —
MonoModule, scenes, prefabs, tickables, cancellation tokens. - Async usage —
IAsyncFunctoid,IAsyncInitializer,ProduceAsync, generator support, cancellation, parallelism.
30-second example
public class MyModule : Module { public MyModule() { Make<int>().From().Instance(42); Make<string>().From().Functoid( IFunctoid.Lift((int n) => $"answer={n}") ); } } var injector = new Injector(ILocator.Empty, IDicsMeasurement.FromDefault(), "demo", new MyModule().Freeze()); ILocator loc = injector.Produce(Key.Of<string>()); Console.WriteLine(loc.Get<string>()); // answer=42
The async equivalent (see docs/async.md):
ILocator loc = await injector.ProduceAsync(ct, Key.Of<string>());
Key concepts
| Concept | What it is |
|---|---|
Key |
A (Type, name?) identifier for an instance. |
Binding |
"How to make this key": instance, ref, functoid, lifecycle. |
Module |
A collection of bindings, plus DSL to build them. Modules concatenate. |
Plan |
A DAG of operations derived from your modules and roots. Validates wiring. |
Locator |
The "world" produced by executing a Plan. |
Injector |
Turns modules into plans and plans into locators. |
Provider |
Runtime-introspectable representation of a function. |
Initializer |
Like a provider, but mutates an existing instance. |
Differences from other DI frameworks
- No circular references. Cycles are unsound, expensive to support, and encourage bad design. DICS rejects them at plan time and reports the cycle chains it found.
- Singleton-only semantics. Non-singleton needs are met via factories, assisted injection, and locator inheritance — all explicit, none implicit.
- No reflection beyond
typeof/Type.Equals. Constructors and initializers are represented as functoids, either hand-written or generated.
Limitations
- DICS internals use mutable references for performance. Treat all DICS data structures as strictly immutable from the outside — mutation will silently corrupt the graph.
- Due to C# constraints, you either lift constructors by hand or use
DICS.Generatorsource generators.
Good practices
- Put
// ReSharper disable PartialTypeWithSinglePartin files withpartialclasses (or disable the inspection globally) so ReSharper does not strippartialfrom generator-driven types.
License
DICS is licensed under the MIT License.