简介
请注意,如果你在寻找Zenject的旧文档,你可以在这里找到。Zenject 3.x, Zenject 4.x 和 Zenject 5.x
Zenject是一个轻量级、高性能的依赖注入框架,专门针对Unity 3D(但它也可以在Unity之外使用)。它可以用来把你的应用程序变成一个松散耦合的部分的集合,具有高度细分的责任。然后,Zenject可以将这些部分以多种不同的配置粘合在一起,使你能够以一种可扩展和极其灵活的方式轻松地编写、重用、重构和测试你的代码。
在以下平台上用Unity 3D测试:
- PC/Mac/Linux
- iOS
- Android
- WebGL
- PS4 (with IL2CPP backend)
- Windows Store (including 8.1, Phone 8.1, Universal 8.1 and Universal 10 - both .NET and IL2CPP backend)
支持IL2CPP,但是有一些问题–详见这里
这个项目是开源的。
对于一般的故障排除/支持,请使用标签 "zenject "发布到 stack overflow,或在zenject谷歌小组中发布。
或者,如果你发现了一个bug,也欢迎你在[github页面]https://github.com/modesttree/Zenject)上创建一个问题,如果你有一个修复/扩展,也可以创建一个拉动请求。也有一个gitter聊天室,你可以加入其中进行实时讨论。
特点介绍
- 注入
- 同时支持普通的C#类和MonoBehaviours
- 构造器注入
- 字段注入
- 属性注入
- 方法注入
- 有条件的绑定(例如,按类型、按名称等)。
- 可选的依赖性
- 支持使用工厂在初始化后创建对象
- 嵌套容器,又称子容器
- 在不同的Unity场景中进行注入,将信息从一个场景传递到另一个场景。
- 场景的父子关系,允许一个场景继承另一个场景的绑定关系。
- 支持全局的、项目范围内的绑定,为所有场景添加依赖性
- 基于约定的绑定,基于类名、命名空间或任何其他标准
- 能够在编辑时验证对象图(包括通过工厂创建的动态对象图)
- 使用ZenjectBinding组件对场景中的组件进行自动绑定
- 使用Moq库的自动模拟
- 对内存池的内置支持
- 支持使用装饰器绑定的装饰器模式
- 支持自动映射开放的泛型类型
- 内置对单元测试、集成测试和场景测试的支持
- 使用LazyInject<>结构的 Just-in-time 注入
- 支持多线程解决/证实问题
- 支持 “反射烘焙”,通过直接修改生成的装配,完全消除昂贵的反射操作。
- 使用ZenAutoInjecter组件对游戏对象进行自动注入
历史
Unity是一个神奇的游戏引擎,然而,鼓励新开发者采取的方法并不适合编写大型、灵活或可扩展的代码库。特别是,Unity管理不同游戏组件之间的依赖关系的默认方式往往是尴尬的和容易出错的。
这个项目的启动是因为当时还没有任何适用于Unity的DI框架,在使用了Unity以外的DI框架(如Ninject)并看到其好处后,我觉得有必要对其进行补救。
最后,我只想说,如果你没有使用DI框架的经验,并且正在编写面向对象的代码,那么相信我,你以后会感谢我的,一旦你学会了如何使用DI编写正确的松散耦合的代码,就再也回不去了。
文档
Zenject的文档分为以下几个部分。它分为两部分(介绍和高级),以便你能尽快开始运行。我建议在开始之前至少先浏览一下介绍部分,然后再根据需要在高级部分*跳转。
另一个很好的起点是观看Infallible Code制作的这个关于zenject的YouTube系列。
你也可以通过玩所提供的样本项目(你可以通过打开Zenject/OptionalExtras/SampleGame1或Zenject/OptionalExtras/SampleGame2找到)而受益。
如果你是DI的老手,那么可能值得看看本页底部的小抄,它应该给你一个语法的概念,这可能是你开始工作所需要的全部。
测试也可能有助于显示每个特定功能的使用情况(你可以在Zenject/OptionalExtras/UnitTests和Zenject/OptionalExtras/IntegrationTests找到)。
也请看进一步阅读部分,了解其他地方提供的一些外部zenject教程。
什么是依赖注入?
理论
以下是我对依赖注入的总体概述。然而,它是轻描淡写的,所以我强烈建议寻求其他资源以获得更多的相关信息,因为有许多其他人(通常有更好的写作能力)写过关于它背后的理论。
当编写一个单独的类来实现某些功能时,它很可能需要与系统中的其他类进行交互以实现其目标。做到这一点的一个方法是让类本身通过调用具体的构造函数来创建其依赖关系。
public class Foo
{
ISomeService _service;
public Foo()
{
_service = new SomeService();
}
public void DoSomething()
{
_service.PerformTask();
…
}
}
这对小项目来说很好,但随着你的项目的增长,它开始变得笨重。Foo类与 "SomeService "类紧密耦合。如果我们后来决定要使用一个不同的具体实现,那么我们就必须回到Foo类中去改变它。
在思考了这个问题之后,往往你会意识到,最终,Foo不应该为选择服务的具体实现细节而烦恼。Foo应该关心的是履行自己的具体职责。只要服务满足了Foo所要求的抽象接口,Foo就很高兴。我们的类就变成了:
public class Foo
{
ISomeService _service;
public Foo(ISomeService service)
{
_service = service;
}
public void DoSomething()
{
_service.PerformTask();
...
}
}
这样做比较好,但现在不管是什么类创建Foo(我们叫它Bar),都有一个问题,就是要填入Foo的额外依赖关系:
public class Bar
{
public void DoSomething()
{
var foo = new Foo(new SomeService());
foo.DoSomething();
...
}
}
而Bar类可能也并不真正关心SomeService Foo的具体实现。因此,我们再次推高了依赖性:
public class Bar
{
ISomeService _service;
public Bar(ISomeService service)
{
_service = service;
}
public void DoSomething()
{
var foo = new Foo(_service);
foo.DoSomething();
...
}
}
所以我们发现,将决定使用哪些类的具体实现的责任在应用程序的 "对象图 "中越推越远是很有用的。如果把这个推到一个极端,我们就会到达应用程序的入口点,在这一点上,所有的依赖都必须在事情开始之前得到满足。应用程序的这一部分的依赖注入行话叫做 “组合根”。它通常看起来像这样:
var service = new SomeService();
var foo = new Foo(service);
var bar = new Bar(service);
var qux = new Qux(bar);
.. etc.
像Zenject这样的DI框架只是帮助自动化创建和分配所有这些具体的依赖关系的过程,这样你就不需要像上面的代码那样明确地自己这样做。
误区
对DI有许多误解,因为一开始它可能很难让你完全理解。这需要时间和经验,才能完全 “理解”。
如上面的例子所示,DI可以被用来轻松地交换一个给定接口的不同实现(在例子中这是ISomeService)。然而,这只是DI提供的众多好处之一。
比这更重要的是,使用Zenject这样的依赖注入框架可以让你更容易地遵循 “单一责任原则”。通过让Zenject担心类的连接问题,类本身可以专注于履行其特定的责任。
另一个刚接触DI的人常犯的错误是,他们从每个类中提取接口,并到处使用这些接口而不是直接使用类。我们的目标是让代码变得更加松散,所以我们有理由认为绑定到一个接口比绑定到一个具体的类更好。然而,在大多数情况下,应用程序的各种职责都有单一的、特定的类来实现,所以在这些情况下使用接口只会增加不必要的维护开销。而且,具体的类已经有一个由其公共成员定义的接口。一个好的经验法则是,只有当类有一个以上的实现,或者你打算在将来有多个实现的情况下,才创建接口(顺便说一下,这被称为重用抽象原则)。
其他好处包括:
-
可重构性–当代码是松耦合的,就像正确使用DI时那样,整个代码库对变化的适应性要强得多。你可以完全改变代码库的一部分,而不会让这些改变对其他部分造成严重的破坏。
-
鼓励模块化代码--当使用DI框架时,你自然会遵循更好的设计实践,因为它迫使你思考类之间的接口。
-
可测试性 - 编写自动化单元测试或用户驱动的测试变得非常容易,因为它只是编写一个不同的 “组合根”,以不同的方式连接依赖关系。想只测试一个子系统?只需创建一个新的组合根。Zenject也有一些支持,以避免组合根本身的代码重复(使用Installers–如下所述)。
Zenject API简介
Hello World实例
using Zenject;
using UnityEngine;
using System.Collections;
public class TestInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<string>().FromInstance("Hello World!");
Container.Bind<Greeter>().AsSingle().NonLazy();
}
}
public class Greeter
{
public Greeter(string message)
{
Debug.Log(message);
}
}
你可以通过以下方式运行这个例子:
- 在Unity中创建一个新的场景
- 在Hierarchy内点击右键,选择Zenject -> Scene Context。
- 在 Project 中的一个文件夹中点击右键,选择创建->Zenject->MonoInstaller。将其命名为TestInstaller.cs
- 将你的TestInstaller脚本添加到场景中(作为它自己的GameObject或与SceneContext在同一个GameObject上,这并不重要)。
- 在SceneContext的属性中添加对TestInstaller的引用,方法是在 "Installers "属性的 inspector 中添加一个新行(按+按钮),然后将TestInstaller拖到其中。
- 打开TestInstaller,将上述代码粘贴到其中
- 通过选择Edit ->Zenject->Validate Current Scene 或点击CTRL+ALT+V来验证你的场景。(注意,这一步不是必须的,但是个好的做法)
- 运行
- 还要注意的是,你可以使用快捷键CTRL+SHIFT+R来 “先验证后运行”。验证通常足够快,这不会给你的游戏运行增加明显的开销,而且当它确实检测到错误时,由于你避免了启动时间,它的迭代速度会快得多。
- 观察unity控制台的输出情况
SceneContext MonoBehaviour是应用程序的入口,在这里Zenject在启动你的场景之前设置了所有的依赖关系。为了给你的Zenject场景添加内容,你需要编写Zenject中所说的 “Installer”,它声明了所有的依赖关系和它们之间的关系。所有被标记为 "NonLazy "的依赖都会在安装程序运行后自动创建,这就是为什么我们上面添加的Greeter类在启动时被创建。如果这对你来说还没有意义,请继续阅读!
注入
有许多不同的方法来声明对容器的依赖关系,这些方法在下一节中都有记录。也有几种方法可以将这些依赖关系注入你的类中。这些方法是:
- 构造函数注入
public class Foo
{
IBar _bar;
public Foo(IBar bar)
{
_bar = bar;
}
}
- 字段注入
public class Foo
{
[Inject]
IBar _bar;
}
字段注入在构造函数被调用后立即发生。所有被标记为 [Inject] 属性的字段都会在容器中被查找并被赋予一个值。请注意,这些字段可以是私有的,也可以是公共的,注入仍然会发生。
- 属性注入
public class Foo
{
[Inject]
public IBar Bar
{
get;
private set;
}
}
属性注入的工作原理与字段注入相同,只不过是应用于C#属性。就像字段一样,在这种情况下,setter 可以是私有的或公共的。
- 方法注射
public class Foo
{
IBar _bar;
Qux _qux;
[Inject]
public void Init(IBar bar, Qux qux)
{
_bar = bar;
_qux = qux;
}
}
方法注入的工作方式与构造函数注入非常相似。
注意以下几点:
- 对于MonoBehaviours,推荐使用注入方法,因为MonoBehaviours不能有构造函数。
- 可以有任何数量的注入方法。在这种情况下,它们是按照基类到派生类的顺序被调用的。这对于避免从派生类通过构造函数参数转发许多依赖关系到基类是很有用的,同时也保证了基类的注入方法首先完成,就像构造函数的工作方式一样。
- 注入方法在所有其他注入类型之后被调用。这样设计是为了让这些方法可以用来执行初始化逻辑,这可能会使用注入的字段或属性。还要注意的是,如果你只想做一些初始化逻辑,你可以让参数列表为空。
- 你可以安全地假设,你通过 inject 方法收到的依赖关系本身已经被注入了(唯一的例外是在你有循环依赖关系的情况下)。如果你使用 inject 方法来执行一些基本的初始化,这可能很重要,因为在这种情况下,你可能需要给定的依赖也被初始化了
- 但是请注意,通常使用方法注入进行初始化逻辑不是一个好主意。通常情况下,最好使用IInitializable.Initialize或Start()方法来代替,因为这将允许首先创建整个初始对象图。
建议
最佳做法是,与字段/属性注入相比,更倾向于构造函数/方法注入。
- 构造函数注入迫使依赖关系只在类创建时被解决一次,这通常是你想要的。在大多数情况下,你不希望为你的初始依赖关系公开一个公共属性,因为这表明它可以被改变。
- 构造函数注入保证了类之间没有循环依赖关系,这通常是一件坏事。然而,Zenject允许在使用其他注入类型时存在循环依赖关系,如方法/字段/属性注入。
- 构造函数/方法注入对于你决定在没有DI框架(如Zenject)的情况下重新使用代码的情况来说,更具可移植性。你可以用公共属性做同样的事情,但它更容易出错(更容易忘记初始化一个字段而使对象处于无效状态)。
- 最后,构造函数/方法注入使得另一个程序员在阅读代码时,可以清楚地知道一个类的所有依赖关系。他们可以简单地看一下方法的参数列表。这也很好,因为当一个类有太多的依赖关系,因此应该被拆分时,它将会更加明显(因为它的构造器参数列表将开始显得很长)。
寄存器对DI容器的映射
依赖注入框架的核心是DI容器。在最简单的形式下,它是一个对象,其中包含一个持有所有注册信息的字典。在本节中,我们将讨论 "注册一个新的映射 "的部分。在Zenject中,它被称为绑定。因为它在抽象和具体类型之间建立了一个绑定。
Binding
每个依赖注入框架最终都只是一个将类型与实例绑定的框架。
在Zenject中,依赖映射是通过向一个叫做容器的东西添加绑定来完成的。然后,这个容器应该 "知道 "如何在你的应用程序中创建所有的对象实例,通过递归解决一个特定对象的所有依赖关系。
当容器被要求构造一个给定类型的实例时,它使用 C# 反射来查找构造函数参数的列表,以及所有标有 [Inject] 属性的字段/属性。然后,它试图解决这些所需的依赖关系,它使用这些依赖关系来调用构造函数并创建新的实例。
因此,每个Zenject应用程序都必须告诉容器如何解决这些依赖关系,这可以通过Bind命令来完成。例如,给定下面这个类:
public class Foo
{
IBar _bar;
public Foo(IBar bar)
{
_bar = bar;
}
}
你可以用下面的方法来连接这个类的依赖关系:
Container.Bind<Foo>().AsSingle();
Container.Bind<IBar>().To<Bar>().AsSingle();
这告诉Zenject,每一个需要依赖Foo类型的类都应该使用相同的实例,它将在需要时自动创建。同样地,任何需要IBar接口的类(如Foo)将被赋予同样的Bar类型的实例。
bind命令的完整格式如下。请注意,在大多数情况下,你不会使用所有这些方法,当没有指定时,它们都有逻辑默认值
Container.Bind<ContractType>()
.WithId(Identifier)
.To<ResultType>()
.FromConstructionMethod()
.AsScope()
.WithArguments(Arguments)
.OnInstantiated(InstantiatedCallback)
.When(Condition)
.(Copy|Move)Into(All|Direct)SubContainers()
.NonLazy()
.IfNotBound();
Where:
-
ContractType = 你正在创建一个绑定的类型。
- 这个值将对应于正在注入的字段/参数的类型。
-
ResultType = 绑定后的类型。
- 默认:ContractType
- 这个类型必须等于ContractType或者是从ContractType派生出来的。如果没有指定,它假定为ToSelf(),这意味着ResultType将与ContractType相同。这个值将被作为构造方法的任何东西所使用,以检索这个类型的一个实例
-
Identifier = 用来唯一识别绑定的值。在大多数情况下,这可以被忽略,但在你需要区分具有相同合同类型的多个绑定的情况下,这可能相当有用。详见这里。
-
ConstructionMethod = 创建/检索ResultType实例的方法。关于各种构造方法的更多细节,见本节。
- 默认: FromNew()
- 例如:FromGetter, FromMethod, FromResolve, FromComponentInNewPrefab, FromSubContainerResolve, FromInstance,等等。
-
Scope = 这个值决定了生成的实例在多次注入中被重复使用的频率(或者是否有)。
- 默认:AsTransient。但是请注意,并不是所有的绑定都有一个默认值,所以如果不提供的话,就会产生一个异常。不需要明确设置范围的绑定是任何具有构造方法的绑定,它是一种搜索,而不是从头开始创建一个新的对象(例如,FromMethod, FromComponentX, FromResolve等)。
- 它可以是以下的一种:
- AsTransient - 完全不会重复使用该实例。每次请求ContractType时,DiContainer将再次执行给定的构造方法。
- AsCached - 将在每次请求ContractType时重新使用相同的ResultType实例,它将在第一次使用时懒惰地生成。
- AsSingle - 与AsCached完全相同,只是如果已经存在一个绑定的结果类型,它有时会抛出异常。它只是一种确保给定的结果类型在容器中是唯一的方法。但是请注意,它只能保证在给定的容器中只有一个实例,这意味着在子容器中使用AsSingle与相同的绑定可以产生第二个实例。
- 在大多数情况下,你可能只想使用AsSingle,然而AsTransient和AsCached也有其用途。
-
Arguments = 构建ResultType类型的新实例时要使用的对象列表。这可以作为一种替代方式,为参数添加其他的绑定,其形式是
Container.BindInstance(arg).WhenInjectedInto<ResultType>()
- InstantiatedCallback = 在某些情况下,能够在一个对象被实例化后对其进行自定义是很有用的。特别是,如果使用一个第三方库,可能需要改变它的一个类型的几个字段。对于这些情况,你可以向OnInstantiated传递一个方法,该方法可以定制新创建的实例。比如说:
Container.Bind<Foo>().AsSingle().OnInstantiated<Foo>(OnFooInstantiated);
void OnFooInstantiated(InjectContext context, Foo foo)
{
foo.Qux = "asdf";
}
或者说,等同于:
Container.Bind<Foo>().AsSingle().OnInstantiated<Foo>((ctx, foo) => foo.Bar = "qux");
请注意,你也可以使用FromFactory绑定一个自定义工厂,在自定义之前直接调用Container.InstantiateX,达到同样的效果,但在某些情况下,OnInstantiated可能更容易。
- Condition = 选择此绑定的条件必须为真。更多细节见这里。
- (Copy|Move)Into(All|Direct)SubContainers = 这个值对99%的用户来说可以忽略。它可以用来自动让子容器继承绑定。例如,如果你有一个Foo类,你想让Foo的唯一实例自动放在容器和每个子容器中,那么你可以添加以下绑定:
Container.Bind<Foo>().AsSingle().CopyIntoAllSubContainers()
换句话说,其结果相当于将Container.Bind().AsSingle()语句复制并粘贴到每个子容器的安装程序中。
或者,如果你只想让Foo在子容器中而不是在当前容器中。
Container.Bind<Foo>().AsSingle().MoveIntoAllSubContainers()
或者,如果你只想让Foo出现在直接的子子容器中,而不是这些子子容器的子容器。
Container.Bind<Foo>().AsSingle().MoveIntoDirectSubContainers()
- NonLazy = 通常情况下,只有在第一次使用绑定时才会实例化ResultType(又称 “懒惰地”)。然而,当NonLazy被使用时,ResultType将在启动时立即被创建。
- IfNotBound = 当这个被添加到一个绑定中,并且已经有一个具有给定合同类型+标识符的绑定,那么这个绑定将被跳过。
构造方法
- FromNew - 通过C#的new操作符创建。如果没有指定构造方法,这就是默认的。
// These are both the same
Container.Bind<Foo>();
Container.Bind<Foo>().FromNew();
- FromInstance - 将一个给定的实例添加到容器中。注意,在这种情况下,给定的实例将不会被注入。如果你也希望你的实例在启动时被注入,请参见 QueueForInject
Container.Bind<Foo>().FromInstance(new Foo());
// You can also use this short hand which just takes ContractType from the parameter type
Container.BindInstance(new Foo());
// This is also what you would typically use for primitive types
Container.BindInstance(5.13f);
Container.BindInstance("foo");
// Or, if you have many instances, you can use BindInstances
Container.BindInstances(5.13f, "foo", new Foo());
- FromMethod - 通过自定义方法创建
Container.Bind<Foo>().FromMethod(SomeMethod);
Foo SomeMethod(InjectContext context)
{
...
return new Foo();
}
- FromMethodMultiple - 与FromMethod相同,只是允许一次返回多个实例(或零)。
Container.Bind<Foo>().FromMethodMultiple(GetFoos);
IEnumerable<Foo> GetFoos(InjectContext context)
{
...
return new Foo[]
{
new Foo(),
new Foo(),
new Foo(),
}
}
- FromFactory - 使用一个自定义的工厂类创建实例。这种构造方法与FromMethod相似,只是在逻辑更复杂或需要依赖性的情况下可以更干净(因为工厂本身可以注入依赖性)。
class FooFactory : IFactory<Foo>
{
public Foo Create()
{
// ...
return new Foo();
}
}
Container.Bind<Foo>().FromFactory<FooFactory>()
- FromIFactory - 使用自定义工厂类创建实例。这是一个比FromFactory更通用、更强大的选择,因为你可以为你的自定义工厂使用任何一种构造方法(不像FromFactory假设FromNew().AsCached())。
例如,你可以使用这样一个工厂,它是一个可编写脚本的对象:
class FooFactory : ScriptableObject, IFactory<Foo>
{
public Foo Create()
{
// ...
return new Foo();
}
}
Container.Bind<Foo>().FromIFactory(x => x.To<FooFactory>().FromScriptableObjectResource("FooFactory")).AsSingle();
或者,你可能想让你的自定义工厂放在一个子容器中,像这样:
public class FooFactory : IFactory<Foo>
{
public Foo Create()
{
return new Foo();
}
}
public override void InstallBindings()
{
Container.Bind<Foo>().FromIFactory(x => x.To<FooFactory>().FromSubContainerResolve().ByMethod(InstallFooFactory)).AsSingle();
}
void InstallFooFactory(DiContainer subContainer)
{
subContainer.Bind<FooFactory>().AsSingle();
}
还请注意,以下两行是等价的:
Container.Bind<Foo>().FromFactory<FooFactory>().AsSingle();
Container.Bind<Foo>().FromIFactory(x => x.To<FooFactory>().AsCached()).AsSingle();
- FromComponentInNewPrefab - 将给定的prefab实例化为一个新的游戏对象,对其注入任何MonoBehaviour,然后以类似于GetComponentInChildren的方式搜索结果类型
Container.Bind<Foo>().FromComponentInNewPrefab(somePrefab);
注意,如果在prefab上有多个匹配的结果类型,它将只匹配遇到的第一个,就像GetComponentInChildren的工作方式一样。因此,如果你要绑定一个prefab,而且没有你想绑定的特定的MonoBehaviour/Component,你可以直接选择Transform,它将匹配prefab的根。
-
FromComponentsInNewPrefab - 和FromComponentInNewPrefab一样,只是会匹配多个值或零值。你可以用它作为例子,然后在某个地方注入List。可以认为与GetComponentsInChildren相似。
-
FromComponentInNewPrefabResource - 将给定的预制件(在给定的资源路径上找到)实例化为一个新的游戏对象,对其注入任何MonoBehaviour,然后以类似于GetComponentInChildren的方式搜索结果类型(因为它将返回找到的第一个匹配值)。
Container.Bind<Foo>().FromComponentInNewPrefabResource("Some/Path/Foo");
-
FromComponentsInNewPrefabResource - 和FromComponentInNewPrefab一样,只是会匹配多个值或零值。你可以用它作为例子,然后在某个地方注入List。可以认为与GetComponentsInChildren类似。
-
FromNewComponentOnNewGameObject - 创建一个新的空游戏对象,然后在其上实例化一个给定类型的新组件。
Container.Bind<Foo>().FromNewComponentOnNewGameObject();
- FromNewComponentOnNewPrefab - 将给定的prefab实例化为一个新的游戏对象,同时在新游戏对象的根上实例化一个给定组件的新实例。注意:预制件不一定包含给定组件的副本。
Container.Bind<Foo>().FromNewComponentOnNewPrefab(somePrefab);
- FromNewComponentOnNewPrefabResource - 实例化给定的prefab(在给定的资源路径上找到),同时在新游戏对象的根上实例化一个给定组件的新实例。注意:预制件不一定包含给定组件的副本。
Container.Bind<Foo>().FromNewComponentOnNewPrefabResource("Some/Path/Foo");
- FromNewComponentOn - 在给定的游戏对象上实例化一个给定类型的新组件。
Container.Bind<Foo>().FromNewComponentOn(someGameObject);
- FromNewComponentSibling - 在当前变换上实例化一个给定的新组件。这里的当前变换取自被注入的对象,因此它必须是MonoBehaviour的派生类型。
Container.Bind<Foo>().FromNewComponentSibling();
请注意,如果给定的组件类型已经附加到当前的变换上,这将只是返回它而不是创建一个新的组件。因此,这个绑定语句的功能类似于Unity的[RequireComponent]属性。
请在这种情况下,ResultType必须派生自UnityEngine.MonoBehaviour/UnityEngine.Component。
还要注意的是,如果一个非MonoBehaviour请求给定的类型,将抛出一个异常,因为在这种情况下没有当前的转换。
- FromComponentInHierarchy - 在与当前上下文相关的场景层次结构中查找组件,以及与任何父上下文相关的层次结构。与GetComponentInChildren类似,它将返回找到的第一个匹配值。
Container.Bind<Foo>().FromComponentInHierarchy().AsSingle();
在最常见的情况下,即上下文是SceneContext,这将搜索整个场景层次结构(除了任何子上下文,如GameObjectContext)。换句话说,当当前的上下文是一个场景上下文时,它的行为类似于GameObject.FindObjectsOfType。注意,由于这可能是一个大的搜索,所以应该谨慎使用,就像GameObject.FindObjectsOfType应该谨慎使用。
在上下文是GameObjectContext的情况下,它将只在游戏对象根部(以及任何父级上下文)内部和下方进行搜索。
在上下文是ProjectContext的情况下,它将只在项目上下文的预制板中进行搜索。
-
FromComponentsInHierarchy - 和FromComponentInHierarchy一样,只是会匹配多个值或零值。你可以用它作为例子,然后在某个地方注入List。可以认为与GetComponentsInChildren类似。
-
FromComponentSibling - 通过搜索附属于当前变换的组件来查找给定的组件类型。这里的当前变换取自被注入的对象,因此它必须是MonoBehaviour的派生类型。
Container.Bind<Foo>().FromComponentSibling();
-
FromComponentsSibling - 与FromComponentSibling相同,只是会匹配多个值或零值。
-
FromComponentInParents - 通过搜索当前的变换或给定组件类型的任何父类来查找组件。这里的当前变换取自被注入的对象,因此它必须是MonoBehaviour的派生类型。
Container.Bind<Foo>().FromComponentInParents();
-
FromComponentsInParents - 和FromComponentInParents一样,只是会匹配多个值或零值。你可以用它作为例子,然后在某个地方注入List。
-
FromComponentInChildren - 通过搜索当前的变换或给定组件类型的任何子变换来查找组件。这里的当前变换取自被注入的对象,因此必须是MonoBehaviour派生类型。与GetComponentInChildren类似,它将返回找到的第一个匹配值。
Container.Bind<Foo>().FromComponentInChildren();
-
FromComponentsInChildren - 和FromComponentInChildren一样,只是会匹配多个值或零值。你可以用它作为例子,然后在某个地方注入List。可以认为与GetComponentsInChildren类似。
-
FromNewComponentOnRoot - 在当前上下文的根上实例化给定的组件。这最常用于GameObjectContext。
Container.Bind<Foo>().FromNewComponentOnRoot();
- FromResource - 通过调用Unity3d函数Resources.Load为ResultType创建。这可以用来加载Resources.Load可以加载的任何类型,如纹理、声音、预制板等。
Container.Bind<Texture>().WithId("Glass").FromResource("Some/Path/Glass");
-
FromResources - 与FromResource相同,只是会匹配多个值或零值。你可以用它来做例子,然后在某个地方注入List。
-
FromScriptableObjectResource - 在给定的资源路径上直接绑定到scriptable object实例。注意:在unity编辑器中对这个值的改变将被持久地保存。如果不希望这样,请使用FromNewScriptableObjectResource。
public class Foo : ScriptableObject
{
}
Container.Bind<Foo>().FromScriptableObjectResource("Some/Path/Foo");
-
FromNewScriptableObjectResource - 与FromScriptableObjectResource相同,只是它将实例化一个给定可脚本对象资源的新副本。如果你想拥有给定的可脚本对象资源的多个不同实例,或者你想确保可脚本对象的保存值不会因为运行时的变化而受到影响,那么这就很有用。
-
FromResolve - 通过对容器进行另一次查找来获取实例(换句话说,调用DiContainer.Resolve())。请注意,为了使其发挥作用,必须在一个单独的绑定语句中绑定 ResultType。当你想把一个接口绑定到另一个接口时,这个构造方法可能特别有用,就像下面的例子所示
public interface IFoo
{
}
public interface IBar : IFoo
{
}
public class Foo : IBar
{
}
Container.Bind<IFoo>().To<IBar>().FromResolve();
Container.Bind<IBar>().To<Foo>();
-
FromResolveAll - 与FromResolve相同,只是会匹配多个值或零值。
-
FromResolveGetter - 从另一个依赖关系的属性中获取实例,该依赖关系是通过对容器进行另一次查找获得的(换句话说,调用 DiContainer.Resolve() 然后访问返回的 ResultType 类型实例上的值)。请注意,为了使其发挥作用,ObjectType 必须在一个单独的绑定语句中被绑定。
public class Bar
{
}
public class Foo
{
public Bar GetBar()
{
return new Bar();
}
}
Container.Bind<Foo>();
Container.Bind<Bar>().FromResolveGetter<Foo>(x => x.GetBar());
-
FromResolveAllGetter - 与FromResolveGetter相同,只是会匹配多个值或零值。
-
FromSubContainerResolve - 通过对子容器进行查找来获取结果类型。需要注意的是,为了使其发挥作用,子容器必须有对 ResultType 的绑定。这种方法可能非常强大,因为它允许你在一个迷你容器中把相关的依赖关系组合在一起,然后只公开某些类(又称 “Facades”),以便在更高层次上操作这组依赖关系。关于使用子容器的更多细节,见本节。有几种方法来定义子容器。
- ByNewPrefabMethod - 通过实例化一个新的prefab来初始化子容器。注意,与ByNewContextPrefab不同,这个绑定方法不要求有一个GameObjectContext连接到prefab。在这种情况下,GameObjectContext是动态添加的,然后用给定的安装器方法运行。
Container.Bind<Foo>().FromSubContainerResolve().ByNewPrefabMethod(MyPrefab, InstallFoo);
void InstallFoo(DiContainer subContainer)
{
subContainer.Bind<Foo>();
}
注意,你也可以不直接传入预制件,而是传入一个getter方法。比如说。
Container.Bind<Foo>().FromSubContainerResolve().ByNewPrefabMethod(ChooseFooPrefab, InstallFoo);
UnityEngine.Object ChooseFooPrefab(InjectContext context) {
return FooPrefabs[Random.Range(0, FooPrefabs.Length)];
}
- ByNewPrefabInstaller – 通过实例化一个新的prefab来初始化子容器。与ByNewPrefabMethod相同,只是它用给定的安装器而不是方法初始化动态创建的GameObjectContext。
Container.Bind<Foo>().FromSubContainerResolve().ByNewPrefabInstaller<FooInstaller>(MyPrefab);
class FooInstaller : Installer
{
public override void InstallBindings()
{
Container.Bind<Foo>();
}
}
注意,你也可以不直接传入预制件,而是传入一个getter方法。比如说。
Container.Bind<Foo>().FromSubContainerResolve().ByNewPrefabInstaller<FooInstaller>(ChooseFooPrefab);
UnityEngine.Object ChooseFooPrefab(InjectContext context) {
return FooPrefabs[Random.Range(0, FooPrefabs.Length)];
}