PlayQ/dics

Extremely efficient and transparent DI for C# and Unity

C#

2

0 commits

updated Aug 28, 2026

See the code

See what people are saying

README

DICS

License: MIT CI

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):

dotnet add package DICS

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:

  1. Compile-time reflection — a Roslyn source generator wraps constructors and initializers into runtime-inspectable functoids. Optional; you can write functoids by hand.
  2. 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.
  3. Production — DICS executes the plan and returns a Locator containing 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

ConceptWhat it is
KeyA (Type, name?) identifier for an instance.
Binding"How to make this key": instance, ref, functoid, lifecycle.
ModuleA collection of bindings, plus DSL to build them. Modules concatenate.
PlanA DAG of operations derived from your modules and roots. Validates wiring.
LocatorThe "world" produced by executing a Plan.
InjectorTurns modules into plans and plans into locators.
ProviderRuntime-introspectable representation of a function.
InitializerLike 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.Generator source generators.

Good practices

  • Put // ReSharper disable PartialTypeWithSinglePart in files with partial classes (or disable the inspection globally) so ReSharper does not strip partial from generator-driven types.

License

DICS is licensed under the MIT License.

PlayQ/dics

Extremely efficient and transparent DI for C# and Unity

C#

2

0 commits

updated Aug 28, 2026

See the code

See what people are saying

README

DICS

License: MIT CI

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):

dotnet add package DICS

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:

  1. Compile-time reflection — a Roslyn source generator wraps constructors and initializers into runtime-inspectable functoids. Optional; you can write functoids by hand.
  2. 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.
  3. Production — DICS executes the plan and returns a Locator containing 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

ConceptWhat it is
KeyA (Type, name?) identifier for an instance.
Binding"How to make this key": instance, ref, functoid, lifecycle.
ModuleA collection of bindings, plus DSL to build them. Modules concatenate.
PlanA DAG of operations derived from your modules and roots. Validates wiring.
LocatorThe "world" produced by executing a Plan.
InjectorTurns modules into plans and plans into locators.
ProviderRuntime-introspectable representation of a function.
InitializerLike 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.Generator source generators.

Good practices

  • Put // ReSharper disable PartialTypeWithSinglePart in files with partial classes (or disable the inspection globally) so ReSharper does not strip partial from generator-driven types.

License

DICS is licensed under the MIT License.

Languages

C#

99.6%