Zenject
0.依据一哥们的总结,反向总结
1.不是吐槽这哥们,是结合这大哥,从不同面向学习Zenject
2.本来无一物,何处惹尘埃
来自哥们的总结
(看得出总结很用心,也是我们依据的关键,比官方文档靠谱,比一些教学视频靠谱)
1、🎠原生的Instantiate游戏对象不会注入其定义的Inject属性,需要使用Container.InstantiatePrefab来实例化,才能受容器管控。
这么写 Kernel 会空了

必须通过Factory 创建(不确定是不是 Zenject 6.x 最新版本的原因)
cs
public void Initialize()
{
//参考代码:
//_prefabsFactory.Create<PlayerMonoEntity>(Address.Prefabs.Player);
prefabsFactory.Create("DataM2LevelSample");
}

2、🌉类似的在SceneManager.LoadScene时,如果Scene没有放置Zenject的SceneContext也无法呗容器管控,其下的对象所定义的Inject属性也无法注入。
SceneContext 倒是真的;和ProjectContext 一样,甚至更加"写死",难道缺了<**ProjectContent>,**Zenject这个框架能跑起来??
3、🚉继承MonoBehavior的单例和普通Csharp单例可以分别如下定义
Container.Bind<GameManager>().FromNewComponentOnNewGameObject().AsSingle(); //自动放置新对象中
Container.Bind<FruitManager>().AsSingle();
4、🌓一些比较贴近Spring的使用
4.1、正常的Inject、IInitializable、 IDisposable接口
4.1.1、MonoBehavior脚本的全局单例
Container.Bind<GameManager>().FromNewComponentOnNewGameObject().AsSingle(); //自动放置新对象中
4.2、ScriptObject数据作为全局配置参数
Container.Bind<BackGroundColorConfigSO>().FromInstance(backGroundColorConfigSO);
Container.Bind<GameManager>().FromNewComponentOnNewGameObject().AsSingle().WithArguments(backGroundColorConfigSO).NonLazy();
4.3、Prefab预制体作为全局配置参数
Container.Bind<GameObject>().WithId("PlayerPrefab").FromInstance(playerPrefab);
Container.Bind<PlayerAttribute>().FromInstance(playerAttribute);
Container.Bind<RespawnPlayerManager>().FromNewComponentOnNewGameObject().AsSingle().WithArguments(playerPrefab, playerAttribute).NonLazy();
(如果你不知道怎么获取 预制体Prefab;我的一个项目,是用上了 addressable 获取Prefab)
cs
//注册
// Config Asset(原来了 GameplayInstaller.cs)
diContainer.Bind<PlayerConfig>()
.FromScriptableObject(service.LoadAsset<PlayerConfig>(Address.Configurations.PlayerConfig))
.AsSingle();
diContainer.Bind<ProjectileConfig>()
.FromScriptableObject(service.LoadAsset<ProjectileConfig>(Address.Configurations.ProjectileConfig))
.AsSingle();
// UIModule Config
diContainer.Bind<RocketConfig>()
.FromScriptableObject(service.LoadAsset<RocketConfig>(Address.Configurations.RocketConfig))
.AsSingle();
5.🎫补充前面问题1~2,Scene 和GameObjectContent 如何关联
待补充
6.内部 Zenject 结构
Zenject DiContainer 打印内部结构(3 种方案)
可以打印容器全部绑定、内部字典、实例缓存、父子容器层级,优先用内置 API,精细化用反射读取私有字段。
*(对比重点如上面说的:正常的Inject、IInitializable、 IDisposable接口 (小哥说的))
其他都不是重点
(也可以看到 Zenject 内部 Context 和 SceneContext是区隔的,如下对比图:)

cs
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using UnityEngine;
using Zenject;
using static Zenject.DiContainer;
public static class ZenjectDebugger
{
public static void PrintAllBindings(DiContainer container)
{
if (container == null)
{
Debug.LogError("ZenjectDebugger: container 为空");
return;
}
// 获取 _providers 字段 (注意是 _providers,不是 _providerMap)
FieldInfo providersField = typeof(DiContainer).GetField("_providers",
BindingFlags.NonPublic | BindingFlags.Instance);
if (providersField == null)
{
Debug.LogError("ZenjectDebugger: 未找到 _providers 字段,可能是 Zenject 版本不兼容");
return;
}
var providers = providersField.GetValue(container) as Dictionary<BindingId, List<ProviderInfo>>;
if (providers == null)
{
Debug.LogWarning("ZenjectDebugger: _providers 为空或类型不匹配");
return;
}
var sb = new StringBuilder();
sb.AppendLine($"=== Zenject 容器绑定信息 (共 {providers.Count} 条) ===");
int index = 1;
foreach (var kvp in providers)
{
BindingId bindingId = kvp.Key;
List<ProviderInfo> providerInfos = kvp.Value;
// BindingId 包含类型和可选标识符
string typeName = bindingId.Type?.Name ?? "null";
string identifier = bindingId.Identifier?.ToString() ?? "无";
sb.AppendLine($"[{index}] 类型: {typeName}");
if (bindingId.Identifier != null)
{
sb.AppendLine($" 标识符: {identifier}");
}
// 打印每个 Provider 的信息
if (providerInfos != null)
{
for (int i = 0; i < providerInfos.Count; i++)
{
ProviderInfo providerInfo = providerInfos[i];
sb.AppendLine($" Provider[{i}]: {GetProviderTypeInfo(providerInfo)}");
}
}
sb.AppendLine();
index++;
}
Debug.Log(sb.ToString());
}
private static string GetProviderTypeInfo(ProviderInfo providerInfo)
{
if (providerInfo == null) return "null";
// 尝试获取实际 Provider 实例
FieldInfo providerInstanceField = typeof(ProviderInfo).GetField("Provider",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (providerInstanceField != null)
{
object provider = providerInstanceField.GetValue(providerInfo);
if (provider != null)
{
return provider.GetType().Name;
}
}
return "Unknown";
}
// 可选:按类型筛选打印
public static void PrintBindingsForType<T>(DiContainer container)
{
PrintBindingsForType(container, typeof(T));
}
public static void PrintBindingsForType(DiContainer container, Type targetType)
{
if (container == null || targetType == null) return;
FieldInfo providersField = typeof(DiContainer).GetField("_providers",
BindingFlags.NonPublic | BindingFlags.Instance);
if (providersField == null) return;
var providers = providersField.GetValue(container) as Dictionary<BindingId, List<ProviderInfo>>;
if (providers == null) return;
var sb = new StringBuilder();
sb.AppendLine($"=== 查找类型 [{targetType.Name}] 的绑定信息 ===");
bool found = false;
foreach (var kvp in providers)
{
if (kvp.Key.Type == targetType || targetType.IsAssignableFrom(kvp.Key.Type))
{
found = true;
string identifier = kvp.Key.Identifier?.ToString() ?? "无";
sb.AppendLine($"类型: {kvp.Key.Type?.Name}, 标识符: {identifier}");
}
}
if (!found)
{
sb.AppendLine("未找到该类型的绑定");
}
Debug.Log(sb.ToString());
}
}
cs
//参考调用代码
ZenjectDebugger.PrintAllBindings(Container);
ZenjectDebugger.PrintAllBindings(ContainerBridge._Container);
6.1
🧩 方法一:容器层次结构是天然的区分线索
Zenject的DiContainer存在父子层次结构,通过ParentContainers属性可以构建容器树。这是框架原生的区分逻辑:子容器会继承父容器的绑定,但可以拥有自己独立的实例。
你可以通过反射获取DiContainer的_parentContainers字段(类型为List<DiContainer>),来打印容器的父子关系:
🧵 方法三:利用上下文与绑定信息倒推容器
如果只是为了调试输出,可以不直接获取容器ID,而是观察它内部绑定的特征,来推断容器身份:
-
检查核心绑定 :
ProjectContext中通常会绑定全局服务(如IAnalyticsService),而SceneContext中则绑定场景特有服务(如ISceneManager)。通过反射查看容器内特定的绑定类型,可以判断容器的类型和用途。 -
检查 Installers :
DiContainer内部有一个_installers字段,存放了已执行安装的安装器实例列表。通过反射读取这个列表,可以知道是哪些Installer向该容器进行了注册,从而推断容器场景归属。
错误1
提供了方法一,和三,你是不是觉得很贴心呢;
同问题一提供了,Instantiate和 PrefabFactory 两个方法
|-----------------------|----------------|
| 不行 | 行? |
| 方法一 | 方法三 |
| Container.Instantiate | _prefabFactory |
错误2
一、核心概念先理清
- Installer :负责向
DiContainer注册依赖的脚本(MonoInstaller/ScriptableObjectInstaller) - Context :Zenject 的容器载体(
ProjectContext/SceneContext/GameObjectContext) - Installer 存储位置 :所有 Installer 最终都绑定在对应的 Context 上,而非直接存在 DiContainer 中
说人话,没办法从 diContainer 获取 installer ,但是Context 可以获取 installer
cs
var allInstaller = ContainerBridge._Container.ResolveAll<Installer>();
int count=0;
foreach(var installer in allInstaller)
{
Debug.Log($"容器已安装{count} Installer: {installer.GetType().Name}");
count++;
}
Debug.Log("====Zenject 容器数量:" + allInstaller.Count);
你是不是觉得又很贴心,又行了呢

结果获取数量为0,根本没用
总结
Zenject 适合什么项目
话不多说了,只说一句:
要是你的项目是一个新的项目
那么Zenject + NewInput + Addressable + UniTask
所有新的框架,Zenject 框架都有提供相关代码和案例,一些案例是有的,甚至是丰富有余;并且有些代码值得参考和学习和借鉴;
🙋♀️参考:
Unity Zenject的一些小Tips(学习备忘)
这哥们还有几个github的开源项目呢
