1

I use C# and Autofac 4.9.4. I have an Autofac Module which hooks up to the IComponentRegistration.Activated event. It looks for activated instances of certain classes and registers them in some manager class. This registration should be limited to the lifetime of the affected objects, of course. So the module needs to know when the object is discarded by the container and then unregister it from the manager. Otherwise I would produce a memory leak. There is a OnRelease-Method when I register a class with the autofac ContainerBuilder, but that is not the right place; I need such an event within the module.

The concrete code looks something like this:

using Autofac;
using Autofac.Core;

namespace De.Gedat.Foundation.Bl.IoC
{

    public class ResetManagerModule : Module
    {
        protected override void AttachToComponentRegistration(
            IComponentRegistry componentRegistry,
            IComponentRegistration registration)
        {
            registration.Activated += (sender, e) =>
                RegisterToResetManager(e.Instance, e.Context);
            registration.Released += ???
        }

        private void RegisterToResetManager(object instance, IComponentContext context)
        {
            // Every IAutoRegisteredResettable object created by IoC will be picked up:
            var resettable = instance as IAutoRegisteredResettable;
            if (resettable == null)
                return;
            // Get the singleton IResetManager...
            var resetManager = context.Resolve<IResetManager>();
            // ...and register the instance with it:
            resetManager.RegisterInstance(resettable);

            // ...and on resettable's end-of-lifetime we would have to call:
            //resetManager.UnregisterInstance(resettable)
            //...but not at this point when the instance has just been created!
        }
    }
}

How can I get noticed when an object is discarded?

JSpot
  • 450
  • 4
  • 12

2 Answers2

0

Assuming a lifetime scope we would have wanted to let autofac handle all the disposing for us, so it was best to wrap the context with a using statement, but it will also dispose the resettable and we won't able then to call resetManager.UnregisterInstance(resettable).

In order not to dispose your resettable instance and still let autofac handle all the disposing when the lifetime scope is being disposed, we can tell autofac that we want to handle it by our own with ExternallyOwned (doc), and in the finally statement we can call resetManager.UnregisterInstance(resettable) and then dispose the instance manually.

private void RegisterToResetManager(object instance, IComponentContext context)
{
    var resettable = instance as IAutoRegisteredResettable;
    if (resettable == null)
        return;
    var resetManager = context.Resolve<IResetManager>();

    try
    {
        resetManager.RegisterInstance(resettable).ExternallyOwned();
    }
    finally
    {
        resetManager.UnregisterInstance(resettable);
        resettable.Dispose();
    }
}
Shahar Shokrani
  • 7,598
  • 9
  • 48
  • 91
  • Thank you for the answer! Yet this would go around the lifetime management offered by the container, and what is more: This module method is called when the resettable instance has just been constructed and not even used. We do not want to dispose the resettable at this point. My comment about the resetManager.UnregisterInstance() may have been misleading in my example; this code must be called when the resettable instance is **destroyed by the container**, but the destruction must not be done in the RegisterToResetManager method. – JSpot Dec 02 '19 at 11:50
  • The concept is still the same, find when the whole container is distroyed and use the ExternallyOwned to prevent the instance from being dispose – Shahar Shokrani Dec 02 '19 at 13:35
  • I can dispose of my instance when I destroy the container, but this is too late because the lifetime must be bound to that of the IAutoRegisteredResettable instance. In a server scenario this would be very short, and I do not want to keep references to old objects. – JSpot Jan 24 '20 at 12:10
0

We came up with the following solution which works perfectly:

public class ResetManagerAutofacModule : Module
{
    protected override void AttachToComponentRegistration(
        IComponentRegistryBuilder componentRegistryBuilder,
        IComponentRegistration registration)
    {
        registration.Activated += (sender, e) =>
            RegisterToResetManager(e.Instance, e.Context);
    }

    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<ResetManager>().As<IResetManager>().SingleInstance();
        // Because there are no module events for the end-of-lifetime of objects,
        // we use a disposable class as a helper to get informed about disposal
        // (declaration see below):
        builder
            .RegisterType<DisposeObserver>()
            .InstancePerDependency(); // important!!!
    }

    private void RegisterToResetManager(object instance, IComponentContext context)
    {
        var resettable = instance as IAutoRegisteredResettable;
        if (resettable == null)
            return;
        var resetManager = context.Resolve<IResetManager>();
        // Hook the object on the manager:
        resetManager.RegisterInstance(resettable);
        // Get a new instance of our dispose helper class from the container
        // which has the same lifetime as "instance" because DisposeObserver
        // is registered as InstancePerDependency.
        var disposableWithCallback = context.Resolve<DisposeObserver>();
        // When this helper is disposed, we know that the lifetime of "instance" is over.
        disposableWithCallback.DisposingCallback =
            // So we can unhook it from the manager:
            () => resetManager.UnregisterInstance(resettable);
    }

    public class DisposeObserver : IDisposable
    {
        public Action DisposingCallback { get; set; }
        public void Dispose()
        {
            DisposingCallback?.Invoke();
            DisposingCallback = null;
        }
    }
}
JSpot
  • 450
  • 4
  • 12