Pregunta

I am using the following interface as a ToFactory() binding:

public interface ISamplerFactory
{
    ISampler Create(Action<EventHandler<ValueChangedEventArgs>> register, Action<EventHandler<ValueChangedEventArgs>> unregister, Func<decimal?> valueGetter);
}

When I bind ToFactory() I can successfully create the class but I then get a memory leak whereby the register, unregister and valueGetter parameters are held by a ConstructorArgument inside Ninject, which reference a target/parameter object inside the delegates. This keeps that target object from getting GC'd. I am using ContextPreservation extension too if that makes a difference. (See complete sample code below)

When I remove the "ToFactory()" bind and create a standard factory class, it works.

public class SamplerFactory : ISamplerFactory
{
    private readonly IDistributionResolver _resolverFactory;

    public SamplerFactory(IDistributionResolverFactory resolverFactory)
    {
        _resolverFactory = resolverFactory;
    }

    ISampler Create(Action<EventHandler<ValueChangedEventArgs>> register, Action<EventHandler<ValueChangedEventArgs>> unregister, Func<decimal?> valueGetter)
    {
        return new SpecificSampler(_resolverFactory, register, unregister, valueGetter);
    }
}

And my target objects inside the delegates are GC'd successfully.

Is there something I am doing wrong or isn't the Factory extension meant to handle these more complex arguments? I assume if I used the .WithConstructorArgument I would get the same outcome.

EDIT: Added all necessary bindings and rewrote my sample code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication
{
    using System;
    using Ninject;
    using Ninject.Extensions.ContextPreservation;
    using Ninject.Extensions.Factory;

    public class Program
    {
        static void Main(string[] args)
        {
            new Program().Run();
        }

        public void Run()
        {
            var kernel = new StandardKernel(new NinjectSettings() { LoadExtensions = false });
            kernel.Load(new FuncModule());
            kernel.Load(new ContextPreservationModule());

            kernel.Bind<IDistributionResolver>().To<DistributionResolver>(); // This is a constructor-less object.
            kernel.Bind<ISampler>().To<SpecificSampler>();
            kernel.Bind<ISamplerFactory>().ToFactory();
            kernel.Bind<IInjected>().To<Injected>();
            kernel.Bind<IDistributionResolver>().To<DistributionResolver>();
            kernel.Bind<IDistributionResolverFactory>().ToFactory();


            var s = new SomeObject();
            var weakS = new WeakReference(s);

            var factory = kernel.Get<ISamplerFactory>();

            var sampler = CreateInstance(factory, s);

            s = null;
            factory = null;
            sampler = null;

            GC.Collect();

            if (weakS.IsAlive)
                throw new Exception();
        }

        private ISampler CreateInstance(ISamplerFactory factory, SomeObject someObject)
        {
            var x = factory.Create(y => someObject.Do += y, z => someObject.Do -= z, () => someObject.Query());

            if (x == null)
                throw new Exception();

            return x;
        }


        public class SomeObject
        {
            public event EventHandler<ValueChangedEventArgs> Do;

            public decimal? Query()
            {
                return 0;
            }
        }

        public class SpecificSampler : ISampler
        {
            private readonly IDistributionResolverFactory resolver;
            private readonly Action<EventHandler<ValueChangedEventArgs>> register;
            private readonly Action<EventHandler<ValueChangedEventArgs>> unregister;
            private Func<decimal?> _valueGetter;

            public SpecificSampler(
                IDistributionResolverFactory resolver, // This is injected
                Action<EventHandler<ValueChangedEventArgs>> register, // The rest come from the factory inputs
                Action<EventHandler<ValueChangedEventArgs>> unregister,
                Func<decimal?> valueGetter)
            {
                this.resolver = resolver;
                this.register = register;
                this.unregister = unregister;
                _valueGetter = valueGetter;
                // Do Stuff;
            }
        }

        public class ValueChangedEventArgs : EventArgs
        {
        }

        public interface ISamplerFactory
        {
            ISampler Create(Action<EventHandler<ValueChangedEventArgs>> register, Action<EventHandler<ValueChangedEventArgs>> unregister, Func<decimal?> valueGetter);
        }

        public interface IDistributionResolverFactory
        {
            IDistributionResolver Create(IDictionary<string, string> picked);
        }

        public interface IDistributionResolver
        {

        }

        private class DistributionResolver : IDistributionResolver
        {
            readonly IInjected _i;
            readonly IDictionary<string, string> _picked;

            public DistributionResolver(IInjected i, IDictionary<string, string> picked)
            {
                _i = i;
                _picked = picked;
            }
        }

        public interface ISampler
        {
        }
    }

    public interface IInjected
    {
    }

    class Injected : IInjected
    {
    }
}
¿Fue útil?

Solución

I tracked this down to a memory leak in Ninject Core:

https://github.com/ninject/ninject/issues/74

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top