Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions website/docs/about/what-is-di.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
title: What is DI ?
---

DI (Dependency Injection) is a general technique in OOP that all about removing unconcerned dependencies from your code.
It brings testability, maintainability, extensibility or any kind of exchangeability to your object graph.
DI (Dependency Injection) is a general technique in OOP that is all about removing unconcerned dependencies from your code.
It brings testability, maintainability, extensibility, and exchangeability to your object graph.

In all programming paradigms, the basic design is, weak module coupling and strong module cohesion.
As you know, OOP (Object Oriented Programming) does it through objects.
1. Objects hides the details of its responsibility (encapsulation).
In all programming paradigms, the basic design goal is loose coupling and strong cohesion.
OOP (Object Oriented Programming) achieves this through objects:
1. Objects hide the details of their responsibility (encapsulation).
2. Objects transfer work outside their responsibilities to other objects.

Actually, there is a problem in doing this fundamentally.
However, there is a fundamental problem with doing this.
If you write the delegation object in the class code, it means tight coupling at the source code level.
The only way to exclude an unconcerned dependency from a class is to pass it in from outside.

Thus, if your class receives dependencies externally, it'll need help from the outside.
DI is a technique that facilitates a place to resolve dependencies completely outside.
Thus, if your class receives dependencies externally, it requires assistance from the outside.
DI is a technique that provides a mechanism to resolve dependencies completely externally.

Further reading:
- [Manning | Dependency Injection in .NET](https://www.manning.com/books/dependency-injection-in-dot-net)
Expand All @@ -24,22 +24,22 @@ Further reading:

## Terminology

- **DI Container:** Have dependent references all at once and execute auto-wiring.
- **Composition Root:** The place where you make settings to resolve dependencies.
- **Auto-wiring:** It is a function that allows you to manage services in the container with minimal configuration. DI library usually does this.
- **IoC (Inversion of Control):** Make the object with control flow responsibility an entry point. In simple and traditional programming, the entry point is where the responsibility for interrupting user input is.
- **DI Container:** Manages dependent references and executes auto-wiring.
- **Composition Root:** The place where you configure dependency resolution.
- **Auto-wiring:** A feature that allows you to manage services in the container with minimal configuration. DI libraries usually handle this.
- **IoC (Inversion of Control):** Making the object with control flow responsibility the entry point. In simple and traditional programming, the entry point is where the responsibility for interpreting user input lies.

## Why DI for Unity ?

In Unity, MonoBehaviour is the entry point for our C# code. On the other hand, MonoBehaviour is also a "View component".

In modern application design, "separation of domain logic and presentation layer (View component)" is important.

It is against this that MonoBehaviour has many roles (event handling, control flow, domain logic calls, etc.) in addition to its behavior as a View.
Ideally, MonoBehaviour should not have so many roles (event handling, control flow, domain logic calls, etc.) in addition to its behavior as a View.

One of the purposes of DI is IoC (Inversion of Control). With DI containers, we can make pure C# classes the entry point (not MonoBehaviour). This means that the control flow and other domain logic can be separated from the function of MonoBehaviour as a view component.
One of the purposes of DI is IoC (Inversion of Control). With DI containers, we can make pure C# classes the entry point (instead of MonoBehaviour). This means that the control flow and other domain logic can be separated from the function of MonoBehaviour as a view component.

View components are dynamically created / destroyed at run time, while all "features" such as control flow and domain logic have a more stable lifespan.
View components are dynamically created and destroyed at runtime, while "features" such as control flow and domain logic have a more stable lifespan.

Generally speaking, it's a good idea to make the View layer stateless and separate it from control flow and data management.

Expand Down
54 changes: 27 additions & 27 deletions website/docs/about/what-is-vcontainer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,30 @@ hide_title: true
import {BenchmarkGraph} from "../../src/components/BenchmarkGraph"
import {GCAllocGraph} from "../../src/components/GCAllocGraph"

The extra fast DI (Dependency Injection) for Unity Game Engine. "V" means making Unity's initial "U" more thinner and solid..!
The extra fast DI (Dependency Injection) for Unity Game Engine. "V" stands for making Unity's initial "U" thinner and more solid!

- **Fast Resolve:** Basically [5-10x faster](#performance) than Zenject.
- **Fast Resolve:** Basically [510x faster](#performance) than Zenject.
- **Minimum GC Allocation:** In Resolve, we have **zero allocation** without spawned instances.
- **Small code size:** Few internal types and few .callvirt.
- **Assisting correct DI way:** Provides simple and transparent API, and carefully select features. This prevents the DI declaration from becoming overly complex.
- **Promotes correct DI patterns:** Provides a simple and transparent API with carefully selected features. This prevents DI declarations from becoming overly complex.
- **Immutable Container:** Thread safety and robustness.

#### Features

- [Constructor Injection](../resolving/constructor-injection) / [Method Injection](../resolving/method-injection) / [Property & Field Injection](../resolving/property-field-injection)
- [Plain C# entry point on own PlayerLoopSystem](../integrations/entrypoint)
- [Flexible scoping](../scoping/lifetime-overview)
- Application can freely create nested Lifetime Scope with any async way for you like.
- [Accelerated mode with Roslyn Source Generator](../optimization/source-generator)
- [Diagnostics Window](../diagnostics/diagnostics-window)
- [UniTask Integration](../integrations/unitask)
- [ECS Integration](../integrations/ecs) *beta*
- [Constructor Injection](../resolving/constructor-injection.mdx) / [Method Injection](../resolving/method-injection.mdx) / [Property & Field Injection](../resolving/property-field-injection.mdx)
- [Plain C# entry point on own PlayerLoopSystem](../integrations/entrypoint.mdx)
- [Flexible scoping](../scoping/lifetime-overview.mdx)
- Applications can freely create nested Lifetime Scopes in any async manner you like.
- [Accelerated mode with Roslyn Source Generator](../optimization/source-generator.mdx)
- [Diagnostics Window](../diagnostics/diagnostics-window.mdx)
- [UniTask Integration](../integrations/unitask.mdx)
- [ECS Integration](../integrations/ecs.mdx) *beta*

## DI + Inversion of Control for Unity

![](assets/vcontainer@2x.png)

DI containers we can make pure C # classes the entry point (not MonoBehaviour). This means that the control flow and other domain logic can be separated from the function of MonoBehaviour as a view component.
With DI containers, we can make pure C# classes the entry point (instead of MonoBehaviour). This means that the control flow and other domain logic can be separated from the function of MonoBehaviour as a view component.

Further reading:
- [Manning | Dependency Injection in .NET](https://www.manning.com/books/dependency-injection-in-dot-net)
Expand All @@ -48,8 +48,8 @@ Further reading:

<BenchmarkGraph height={260} />

- By default, both VContainer and Zenject use reflection at runtime.
- "VContainer (CodeGen)" means optimization by pre-generating IL code of Inject methods by ILPostProcessor. See [Optimization](../optimization/codegen) section for more information.
- By default, VContainer and Zenject use reflection at runtime.
- "VContainer (CodeGen)" means optimization by pre-generating IL code of Inject methods by ILPostProcessor. See [Optimization](../optimization/codegen.mdx) section for more information.

### GC Alloc result in the Resolve Complex test case (Unity Editor profiled)

Expand All @@ -74,7 +74,7 @@ public class GameLifetimeScope : LifetimeScope
}
```

Where definitions of classes are
Where the class definitions are:

```csharp
public interface IRouteSearch
Expand Down Expand Up @@ -120,13 +120,13 @@ public class ActorPresenter : IStartable

void IStartable.Start()
{
// Scheduled at Start () on VContainer's own PlayerLoopSystem.
// Scheduled at Start() on VContainer's own PlayerLoopSystem.
}
}
```

- In this example, the routeSearch of CharacterService is automatically set as the instance of AStarRouteSearch when CharacterService is resolved.
- Further, VContainer can have a Pure C# class as an entry point. (Various timings such as Start, Update, etc. can be specified.) This facilitates "separation of domain logic and presentation".
- In this example, the `routeSearch` field of `CharacterService` is automatically set as the instance of `AStarRouteSearch` when `CharacterService` is resolved.
- Furthermore, VContainer can use a pure C# class as an entry point. (Various timings such as Start, Update, etc., can be specified.) This facilitates "separation of domain logic and presentation".

### Flexible Scoping with async

Expand All @@ -140,7 +140,7 @@ public void LoadLevel()
// Create a child scope
instantScope = currentScope.CreateChild();

// Create a child scope with LifetimeScope prefab
// Create a child scope with a LifetimeScope prefab
instantScope = currentScope.CreateChildFromPrefab(lifetimeScopePrefab);

// Create a child with additional registration
Expand Down Expand Up @@ -174,7 +174,7 @@ class SceneLoader

public SceneLoader(LifetimeScope currentScope)
{
currentScope = currentScope; // Inject the LifetimeScope to which this class belongs
this.currentScope = currentScope; // Inject the LifetimeScope to which this class belongs
}

IEnumerator LoadSceneAsync()
Expand All @@ -194,7 +194,7 @@ class SceneLoader
// UniTask example
async UniTask LoadSceneAsync()
{
using (LifetimeScope.EnqueueParent(parent))
using (LifetimeScope.EnqueueParent(currentScope))
{
await SceneManager.LoadSceneAsync("...", LoadSceneMode.Additive);
}
Expand All @@ -214,7 +214,7 @@ using (LifetimeScope.Enqueue(builder =>
}
```

See [scoping](../scoping/lifetime-overview) for more information.
See [scoping](../scoping/lifetime-overview.mdx) for more information.

## UniTask

Expand All @@ -234,16 +234,16 @@ public class FooController : IAsyncStartable
builder.RegisterEntryPoint<FooController>();
```

See [integration](../integrations/unitask) for more information.
See [integration](../integrations/unitask.mdx) for more information.

## Diagnostics Window

![](../diagnostics/assets/screenshot_diagnostics_window.png)

See [diagnostics](../diagnostics/diagnostics-window) for more information.
See [diagnostics](../diagnostics/diagnostics-window.mdx) for more information.

## Getting Started

- [Installation](../getting-started/installation)
- [Hello World](../getting-started/hello-world)
- [Comparing to Zenject](../comparing/comparing-to-zenject)
- [Installation](../getting-started/installation.mdx)
- [Hello World](../getting-started/hello-world.mdx)
- [Comparing to Zenject](../comparing/comparing-to-zenject.mdx)
160 changes: 160 additions & 0 deletions website/docs/best-practices.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
title: Best Practices
---

This section introduces best practices for using VContainer effectively and writing clean, maintainable code.

## Registration

### Prefer `Register` over `Install` methods

Users migrating from other DI libraries (like Zenject) might be tempted to create "Installer" classes or methods named `Install`.

**Bad Practice:**
```csharp
public class GameLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
// Don't do this
new MyGameInstaller().Install(builder);
}
}

public class MyGameInstaller
{
public void Install(IContainerBuilder builder)
{
builder.Register<ServiceA>(Lifetime.Singleton);
}
}
```

**Best Practice:**
Use C# Extension Methods on `IContainerBuilder` to group registrations. This keeps the API consistent with the rest of VContainer.

```csharp
// Define an extension method
public static class MyGameContainerExtensions
{
public static void RegisterMyGameServices(this IContainerBuilder builder)
{
builder.Register<ServiceA>(Lifetime.Singleton);
builder.Register<ServiceB>(Lifetime.Singleton);
}
}

public class GameLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
// Use it like a native method
builder.RegisterMyGameServices();
}
}
```

### Explicit Lifetime

Always specify the `Lifetime` explicitly. While some libraries default to Singleton or Transient, VContainer requires you to be explicit to avoid ambiguity and accidental object lifecycle issues.

```csharp
// Clear and readable
builder.Register<MyService>(Lifetime.Singleton);
```

## Injection

### Constructor Injection is King

Constructor injection is the most robust form of injection. It ensures that a class cannot be instantiated without its dependencies, making it "complete" and testable.

**Best Practice:**
```csharp
public class PlayerController
{
readonly IInputService inputService;

// The dependencies are clear just by looking at the constructor
public PlayerController(IInputService inputService)
{
this.inputService = inputService;
}
}
```

### Avoid Property/Field Injection Where Possible

Property and Field injection should generally be reserved for MonoBehaviour classes where you cannot control the constructor (though VContainer can often handle MonoBehaviours too). Field injection hides dependencies and makes unit testing harder because you have to manually set private fields via reflection or helper methods.

### Avoid Service Locator Pattern

Do not inject `IObjectResolver` or `IContainer` into your classes to resolve dependencies manually. This hides what the class actually needs and tightly couples your class to the DI container.

**Bad Practice:**
```csharp
public class BadClass
{
readonly IObjectResolver container;

public BadClass(IObjectResolver container)
{
this.container = container;
}

public void DoSomething()
{
// Hidden dependency!
var service = container.Resolve<IMyService>();
service.Run();
}
}
```

**Best Practice:**
Request `IMyService` directly in the constructor.

## Lifecycle Management

### Use Interfaces for Entry Points

Instead of using `MonoBehaviour`'s `Start`, `Update`, `OnDestroy` for your pure C# logic, use VContainer's marker interfaces: `IStartable`, `ITickable`, `IDisposable`, etc.

This decouples your game logic from the Unity Engine (making it testable outside Unity) and provides a unified entry point.

```csharp
public class GamePresenter : IStartable, ITickable, IDisposable
{
public void Start() { /* Init */ }
public void Tick() { /* Update loop */ }
public void Dispose() { /* Cleanup */ }
}
```

Register it with:
```csharp
builder.RegisterEntryPoint<GamePresenter>();
```

### Leverage Scoping

Don't dump everything into a single Root `LifetimeScope`. Use child scopes for specific contexts like:
- A specific game level
- A dynamically loaded character
- A UI screen

When the scope is disposed (e.g., the GameObject is destroyed or the scene unloads), all `IDisposable` instances created within that scope are automatically disposed. This is excellent for memory management.

## Project Structure

### Composition Root

Keep your `Configure` method in `LifetimeScope` clean. It is your **Composition Root**. It should only contain registration logic. Avoid putting game initialization logic inside `Configure`. Use `IStartable` for initialization.

### Pure C# vs MonoBehaviours

Try to keep as much logic as possible in pure C# classes registered in the container. Use `MonoBehaviour` mainly for:
- View components (referencing UI elements, Transforms, Colliders).
- Bridge components that forward Unity events (Collision, Trigger) to your pure C# classes.

This separation concerns (View vs Logic) follows the MVP/MVVM patterns and makes your codebase much more maintainable.
Loading
Loading