Skip to content

Latest commit

 

History

History
392 lines (292 loc) · 13.7 KB

CheatSheet.md

File metadata and controls

392 lines (292 loc) · 13.7 KB
Details
  • Installers Cheat-Sheet

Installers Cheat-Sheet

Below are a bunch of randomly assorted examples of bindings that you might include in one of your installers.

For more examples, you may also be interested in reading some of the Unit tests (see Zenject/OptionalExtras/UnitTests and Zenject/OptionalExtras/IntegrationTests directories)

public override void InstallBindings()
{
    // Create a new instance of Foo for every class that asks for it
    Container.Bind<Foo>().AsTransient();

    // Create a new instance of Foo for every class that asks for an IFoo
    Container.Bind<IFoo>().To<Foo>().AsTransient();

    // Non generic version of the above
    Container.Bind(typeof(IFoo)).To(typeof(Foo)).AsTransient();

    ///////////// AsSingle

    // Create one definitive instance of Foo and re-use that for every class that asks for it
    Container.Bind<Foo>().AsSingle();

    // Create one definitive instance of Foo and re-use that for every class that asks for IFoo
    Container.Bind<IFoo>().To<Foo>().AsSingle();

    // Bind the same instance to multiple types
    // In this example, the same instance of Foo will be used for all three types
    // (we have to use the non-generic version of Bind when mapping to multiple types)
    Container.Bind(typeof(Foo), typeof(IFoo), typeof(IFoo2)).To<Foo>().AsSingle();

    ///////////// BindInterfaces

    // This will have the exact same effect as the above line
    // Bind all interfaces that Foo implements and Foo itself to a new singleton of type Foo
    Container.BindInterfacesAndSelfTo<Foo>().AsSingle();

    // Bind only the interfaces that Foo implements to an instance of Foo
    // This can be useful if you don't want any classes to directly reference the concrete
    // derived type
    Container.BindInterfacesTo<Foo>().AsSingle();

    ///////////// FromInstance

    // Use the given instance everywhere that Foo is used
    // Note that in this case there's no good reason to use FromInstance
    Container.Bind<Foo>().FromInstance(new Foo());

    // This is simply a shortcut for the above binding
    // This can be a bit nicer since the type argument can be deduced from the parameter
    Container.BindInstance(new Foo());

    // Bind multiple instances at once
    Container.BindInstances(new Foo(), new Bar());

    ///////////// Binding primitive types

    // BindInstance is more commonly used with primitive types
    // Use the number 10 every time an int is requested
    Container.Bind<int>().FromInstance(10);
    Container.Bind<bool>().FromInstance(false);

    // Or equivalently:
    Container.BindInstance(10);
    Container.BindInstance(false);

    // You'd never really want to do the above though - you should almost always use a When condition for primitive values
    Container.BindInstance(10).WhenInjectedInto<Foo>();

    ///////////// FromMethod

    // Create instance of Foo when requested, using the given method
    // Note that for more complex construction scenarios, you might consider using a factory
    // instead with FromFactory
    Container.Bind<Foo>().FromMethod(GetFoo);

    // Randomly return one of several different implementations of IFoo
    // We use Instantiate here instead of just new so that Foo1 gets its members injected
    Container.Bind<IFoo>().FromMethod(GetRandomFoo);

    // You an also use an anonymouse delegate directly
    Container.Bind<Foo>().FromMethod(ctx => new Foo());

    // This is equivalent to AsTransient
    Container.Bind<Foo>().FromMethod(ctx => ctx.Container.Instantiate<Foo>());

    InstallMore();
}

Foo GetFoo(InjectContext ctx)
{
    return new Foo();
}

IFoo GetRandomFoo(InjectContext ctx)
{
    switch (UnityEngine.Random.Range(0, 3))
    {
        case 0:
        {
            return ctx.Container.Instantiate<Foo1>();
        }
        case 1:
        {
            return ctx.Container.Instantiate<Foo2>();
        }
    }

    return ctx.Container.Instantiate<Foo3>();
}

void InstallMore()
{
    ///////////// FromResolveGetter

    // Bind to a property on another dependency
    // This can be helpful to reduce coupling between classes
    Container.Bind<Foo>().AsSingle();

    Container.Bind<Bar>().FromResolveGetter<Foo>(foo => foo.GetBar());

    // Another example using values
    Container.Bind<string>().FromResolveGetter<Foo>(foo => foo.GetTitle());

    ///////////// FromNewComponentOnNewGameObject

    // Create a new game object at the root of the scene and add the Foo MonoBehaviour to it
    Container.Bind<Foo>().FromNewComponentOnNewGameObject().AsSingle();

    // You can also specify the game object name to use using WithGameObjectName
    Container.Bind<Foo>().FromNewComponentOnNewGameObject().WithGameObjectName("Foo1").AsSingle();

    // Bind to an interface instead
    Container.Bind<IFoo>().To<Foo>().FromNewComponentOnNewGameObject().AsSingle();

    ///////////// FromComponentInNewPrefab (singleton)

    // Create a new game object at the root of the scene using the given prefab
    // After zenject creates a new GameObject from the given prefab, it will
    // search the prefab for a component of type 'Foo' and return that
    GameObject prefab = null;
    Container.Bind<Foo>().FromComponentInNewPrefab(prefab).AsSingle();

    // Bind to interface instead
    Container.Bind<IFoo>().To<Foo>().FromComponentInNewPrefab(prefab).AsSingle();

    // You can also add multiple components
    // Note here that only one instance of the given prefab will be
    // created
    // For this to work, there must be both a Foo MonoBehaviour and
    // a Bar MonoBehaviour somewhere on the prefab
    Container.Bind(typeof(Foo), typeof(Bar)).FromComponentInNewPrefab(prefab).AsSingle();

    ///////////// FromComponentInNewPrefab (Transient)

    // Instantiate a new copy of 'prefab' every time an instance of Foo is
    // requested by a constructor parameter, injected field, etc.
    Container.Bind<Foo>().FromComponentInNewPrefab(prefab).AsTransient();

    // Bind to interface instead
    Container.Bind<IFoo>().To<Foo>().FromComponentInNewPrefab(prefab);

    ///////////// Identifiers

    // Bind a globally accessible string with the name 'PlayerName'
    // Note however that a better option might be to create a Settings object and bind
    // that instead
    Container.Bind<string>().WithId("PlayerName").FromInstance("name of the player");

    // This is the equivalent of the line above, and is a bit more readable
    Container.BindInstance("name of the player").WithId("PlayerName");

    // We can also use IDs to bind multiple instances of the same type:
    Container.BindInstance("foo").WithId("FooA");
    Container.BindInstance("asdf").WithId("FooB");

    InstallMore2();
}

// Then when we inject these dependencies we have to use the same ID:
public class Norf
{
    [Inject(Id = "FooA")]
    string _foo;
}

public class Qux
{
    [Inject(Id = "FooB")]
    string _foo;
}

public void InstallMore2()
{
    ///////////// AsCached

    // In this example, we bind three instances of Foo, including one without an ID
    // We have to use AsCached here because Foo is not a singleton, but we also
    // do not want a new Foo created every time like AsTransient
    // This will result in a maximum of 3 instances of Foo
    Container.Bind<Foo>().AsCached();
    Container.Bind<Foo>().WithId("FooA").AsCached();
    Container.Bind<Foo>().WithId("FooA").AsCached();

    InstallMore3();
}

// When an ID is unspecified in an [Inject] field, it will use the first
// instance
// Bindings without IDs can therefore be used as a default and we can
// specify IDs for specific versions of the same type
public class Norf2
{
    [Inject]
    Foo _foo;
}

// Qux2._foo will be the same instance as Norf2._foo
// This is because we are using AsCached rather than AsTransient
public class Qux2
{
    [Inject]
    Foo _foo;

    [Inject(Id = "FooA")]
    Foo _foo2;
}

public void InstallMore3()
{
    ///////////// Conditions

    // This will make Foo only visible to Bar
    // If we add Foo to the constructor of any other class it won't find it
    Container.Bind<Foo>().AsSingle().WhenInjectedInto<Bar>();

    // Use different implementations of IFoo dependending on which
    // class is being injected
    Container.Bind<IFoo>().To<Foo1>().AsSingle().WhenInjectedInto<Bar>();
    Container.Bind<IFoo>().To<Foo2>().AsSingle().WhenInjectedInto<Qux>();

    // Use "Foo1" as the default implementation except when injecting into
    // class Qux, in which case use Foo2
    // This works because if there is a condition match, that takes precedence
    Container.Bind<IFoo>().To<Foo1>().AsSingle();
    Container.Bind<IFoo>().To<Foo2>().AsSingle().WhenInjectedInto<Qux>();

    // Allow depending on Foo in only a few select classes
    Container.Bind<Foo>().AsSingle().WhenInjectedInto(typeof(Bar), typeof(Qux), typeof(Baz));

    // Supply "my game" for any strings that are injected into the Gui class with the identifier "Title"
    Container.BindInstance("my game").WithId("Title").WhenInjectedInto<Gui>();

    // Supply 5 for all ints that are injected into the Gui class
    Container.BindInstance(5).WhenInjectedInto<Gui>();

    // Supply 5 for all ints that are injected into a parameter or field
    // inside type Gui that is named 'width'
    // Note that this is usually not a good idea since the name of a field can change
    // easily and break the binding but shown here as an example of a more complex
    // condition
    Container.BindInstance(5.0f).When(ctx =>
        ctx.ObjectType == typeof(Gui) && ctx.MemberName == "width");

    // Create a new 'Foo' for every class that is created as part of the
    // construction of the 'Bar' class
    // So if Bar has a constructor parameter of type Qux, and Qux has
    // a constructor parameter of type IFoo, a new Foo will be created
    // for that case
    Container.Bind<IFoo>().To<Foo>().AsTransient().When(
        ctx => ctx.AllObjectTypes.Contains(typeof(Bar)));

    ///////////// Complex conditions example

    var foo1 = new Foo();
    var foo2 = new Foo();

    Container.Bind<Bar>().WithId("Bar1").AsCached();
    Container.Bind<Bar>().WithId("Bar2").AsCached();

    // Here we use the 'ParentContexts' property of inject context to sync multiple corresponding identifiers
    Container.BindInstance(foo1).When(c => c.ParentContexts.Where(x => x.MemberType == typeof(Bar) && x.Identifier == "Bar1").Any());
    Container.BindInstance(foo2).When(c => c.ParentContexts.Where(x => x.MemberType == typeof(Bar) && x.Identifier == "Bar2").Any());

    // This results in:
    Assert.That(Container.ResolveId<Bar>("Bar1").Foo == foo1);
    Assert.That(Container.ResolveId<Bar>("Bar2").Foo == foo2);

    ///////////// FromResolve

    // FromResolve does another lookup on the container
    // This will result in IBar, IFoo, and Foo, all being bound to the same instance of
    // Foo which is assume to exist somewhere on the given prefab
    GameObject fooPrefab = null;
    Container.Bind<Foo>().FromComponentInNewPrefab(fooPrefab).AsSingle();
    Container.Bind<IBar>().To<Foo>().FromResolve();
    Container.Bind<IFoo>().To<IBar>().FromResolve();

    // This will result in the same behaviour as the above
    Container.Bind(typeof(Foo), typeof(IBar), typeof(IFoo)).To<Foo>().FromComponentInNewPrefab(fooPrefab).AsSingle();

    InstallMore4();
}

public class FooInstaller : Installer<FooInstaller>
{
    public FooInstaller(string foo)
    {
    }

    public override void InstallBindings()
    {
    }
}

public class FooInstallerWithArgs : Installer<string, FooInstallerWithArgs>
{
    public FooInstallerWithArgs(string foo)
    {
    }

    public override void InstallBindings()
    {
    }
}

void InstallMore4()
{
    ///////////// Installing Other Installers

    // Immediately call InstallBindings() on FooInstaller
    FooInstaller.Install(Container);

    // Before calling FooInstaller, configure a property of it
    Container.BindInstance("foo").WhenInjectedInto<FooInstaller>();
    FooInstaller.Install(Container);

    // The arguments can also be added to the Installer<> generic arguments to make them
    // strongly typed
    FooInstallerWithArgs.Install(Container, "foo");

    ///////////// Manual Use of Container

    // This will fill in any parameters marked as [Inject] and also call any [Inject] methods
    var foo = new Foo();
    Container.Inject(foo);

    // Return an instance for IFoo, using the bindings that have been added previously
    // Internally it is what is triggered when you fill in a constructor parameter of type IFoo
    // Note: It will throw an exception if it cannot find a match
    Container.Resolve<IFoo>();

    // Same as the above except returns null when it can't find the given type
    Container.TryResolve<IFoo>();

    // Return a list of 2 instances of type Foo
    // Note that in this case simply calling Resolve<IFoo> will trigger an exception
    Container.BindInstance(new Foo());
    Container.BindInstance(new Foo());
    var foos = Container.ResolveAll<IFoo>();

    // Create a new instance of Foo and inject on any of its members
    // And fill in any constructor parameters Foo might have
    Container.Instantiate<Foo>();

    GameObject prefab1 = null;
    GameObject prefab2 = null;

    // Instantiate a new prefab and have any injectables filled in on the prefab
    GameObject go = Container.InstantiatePrefab(prefab1);

    // Instantiate a new prefab and return a specific monobehaviour
    Foo foo2 = Container.InstantiatePrefabForComponent<Foo>(prefab2);

    // Add a new component to an existing game object
    Foo foo3 = Container.InstantiateComponent<Foo>(go);
}