Dependency Management
As this framework is built to enable plugin development and work on any platform/framework, there has been some efforts to streamline how DI is handled within the framework by creating an abstraction over the underlying DI system.
But why?
So lets say you wanted to create an RPG plugin where it contained components, events, systems etc for handling buffs, items, inventory etc. Now none of this logic really is dependent on Unity, Monogame etc... its all just raw .net, but you will want to setup the DI concerns for this plugin so it can be loaded into any of those platforms and just work.
To be able to do that we need to have the notion of DI in the framework without having an ACTUAL DI container available. As there would be no point having a hard dependency on Zenject for your plugin if you wanted to consume it in the Monogame world etc.
Functionality
As not all DI frameworks provide the same syntax/feature set we have streamlined the approach to include most common scenarios, and will add to it if needed. Here is a list of the container and config supported features.
Container Features
Binding - Bind<From, To>
, Bind<T>
, Unbind<T>
Bind<From, To>
, Bind<T>
, Unbind<T>
This lets you bind a given type to another type, so it could be Bind<ISomething, Something>()
or if you want to self bind the concrete type just do Bind<Something>()
. You can also unbind a type by using Unbind<Something>()
. You can also add named bindings and other configuration by passing in a configuration object (discussed further on).
Resolving - Resolve<T>
, ResolveAll<T>
Resolve<T>
, ResolveAll<T>
This lets you get an instance of something from the DI container, if you want a single instance do Resolve<T>()
or if you want all instances matching that type do ResolveAll<T>()
which returns an enumerable of the given type. You can also request an instance of a type with a given name providing you have bound the type with a name, by doing Resolve<ISomething>("something-1")
which would return the implementation of Something
named "something-1".
Modules/Setup - LoadModule<T>
, LoadModule(IDependencyModule)
LoadModule<T>
, LoadModule(IDependencyModule)
There is also support for creating modules that setup your DI configuration, this requires you to implement IDependencyModule
and has a Setup
method which provides you the container to setup your bindings on.
Binding Configuration
If you want to configure how a binding should work, you can pass into the Bind
methods an optional configuration object which exposes the current properties.
AsSingleton: bool
bool
Setting this to true
will mean that only one instance of this binding should exist, so if you were to do:
Then resolve IEventSystem
in multiple places you will always get back the same instance, which is extremely handy for infrastructure style objects which should act as singletons. If you provide false
as the value it will return a new instance for every resolve request.
WithName: string
string
This will allow you to give the binding a name for resolving via name.
OnActivation: Action<IDependencyContainer, object>
Action<IDependencyContainer, object>
This method will fire when an instance has been activated within the DI container.
WhenInjectedInto: Type || IEnumerable<Type>
Type || IEnumerable<Type>
This will only inject the given binding in when the requesting type matches the types provided.
ToInstance: object
object
This allows you to bind to an actual instance of an object rather than a type, which is useful if you need to manually setup something yourself.
ToMethod: Func<IDependencyContainer, object>
Func<IDependencyContainer, object>
This allows you to lazy bind something to a method rather than an instance/type, which is useful if you want to setup something in a custom way once all DI configuration has been processed.
There is a slightly nicer way to set this up using a builder pattern extension shown further on.
WithNamedConstructorArgs: IDictionary<string, object>
IDictionary<string, object>
This allows you to provide an argument for the constructor of the type based off its argument name.
WithTypedConstructorArgs: IDictionary<Type, object>
IDictionary<Type, object>
This allows you to provide an argument for the constructor of the type based off its argument type.
Extension helpers
The configuration object is simple but can be unsightly for larger configurations, to assist in this we have added a couple of extension methods which can be used to make your binding config a little more succinct.
Builder helpers
There is a builder pattern helper which lets you setup your binding config via a builder rather than an instance of BindingConfiguration
, this can be used by just creating a lambda within the bind method like so:
This lets you setup the configuration in a nice way, it also has type safety so you can setup instances and methods using it like so:
Other helpers
There is also another helper which allows you to get an IObservableGroup
directly from the container, this internally gets the instance of the IEntityCollectionManager
and requests an observable group of a given type like so:
This can be handy when you want to setup Computed
objects which computed from a group, an example of this can be seen in the Roguelike example.
Last updated