Zenject icon indicating copy to clipboard operation
Zenject copied to clipboard

IDisposable installed AsTransient is not disposed

Open Nightz12 opened this issue 4 years ago • 3 comments

Describe the bug When having an AsTransient binding for an IDisposable class in an installer it automatically creates an instance (like non-lazy initialization). But this instance is not entered into the DisposableManager and therfore gets not disposed when all dynamically created instances of the class are disposed.

To Reproduce Have a class inheriting from IDisposable. Add it to an installer with AsTransient. This already creates an instance once the installer is executed, but the instance will never call its Dispose().

Expected behavior Either IDisposable classes when installed AsTransient are not automatically creating an instance. Or otherwise IDisposable classes instances created in that way should be added to the DisposableManager automatically, so they are disposed correctly.

Extenject and Unity info (please complete the following information):

  • Zenject version: 9.2.0
  • Unity version: 2018.4.3 annd 2020.3.6
  • Project's scripting backend [e.g. Mono/IL2CPP]: Mono

Nightz12 avatar May 27 '21 08:05 Nightz12

Experiencing the same issue. I also found that even though I have 2 places I use a transient dependency it gets created 3 times. Then on scene change only the third (redundant one) gets disposed.

coder89 avatar Jul 14 '21 07:07 coder89

After some debugging I think I found a root cause: when IDisposable is used together with AsTransient() a new instance will be created and passed to DisposableManager. It simply won't even try to get existing instances issued by the TransientProvider - this is what AsTransient does really and there is no special treatment for interfaces like IDisposable IInitializable and ITickable.

This would need some rework on how transpent dependencies handling works.

In the meantime as a temporary workaround I would suggest creating a factory that would hold references to all transient instances created (registered AsSingle with IDisposable/tickable/initializable) and then it would call respective methods on the actual transient dependencies.

coder89 avatar Jul 14 '21 08:07 coder89

Here is an example workaround:

    public sealed class DisposableTracker<T> : IDisposable where T : IDisposable
    {
        private readonly List<T> objects = new List<T>();

        [Preserve]
        public DisposableTracker() { }

        public void Dispose()
        {
            this.objects.ForEach(m => m.Dispose());
            this.objects.Clear();
        }

        public void Track(T instance)
        {
            this.objects.Add(instance);
        }
    }

And then in Install bindings method in your installer

                var disposableTracker = new DisposableTracker<Foo>();
                this.Container.BindInterfacesTo<DisposableTracker<Foo>>()
                              .FromInstance(disposableTracker);
                this.Container.Bind<Foo>()
                              .AsTransient()
                              .OnInstantiated<Foo>((ctx, instance) => disposableTracker.Track(instance));

coder89 avatar Jul 14 '21 08:07 coder89