(This question was first asked in the Ninject Google Group, but I see now that Stackoverflow seems to be more active.)
I'm using the NamedScopeExtension to inject the same ViewModel into both the View and the Presenter. After the View have been released, memory profiling shows that the ViewModel is still retained by the Ninject cache. How can I make Ninject release the ViewModel? All ViewModels are released when the Form is Closed and Disposed, but I'm creating and deleting Controls using a Factory in the Form and would like the ViewModels be garbage collected to (the Presenter and View gets collected).
See the following UnitTest, using dotMemoryUnit, for an illustration of the problem:
using System;
using FluentAssertions;
using JetBrains.dotMemoryUnit;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Ninject;
using Ninject.Extensions.DependencyCreation;
using Ninject.Extensions.NamedScope;
namespace UnitTestProject
{
[TestClass]
[DotMemoryUnit(FailIfRunWithoutSupport = false)]
public class UnitTest1
{
[TestMethod]
public void TestMethod()
{
// Call in sub method so no local variables are left for the memory profiling
SubMethod();
// Assert
dotMemory.Check(m =>
{
m.GetObjects(w => w.Type.Is<ViewModel>()).ObjectsCount.Should().Be(0);
});
}
private static void SubMethod()
{
// Arrange
var kernel = new StandardKernel();
string namedScope = "namedScope";
kernel.Bind<View>().ToSelf()
.DefinesNamedScope(namedScope);
kernel.DefineDependency<View, Presenter>();
kernel.Bind<ViewModel>().ToSelf()
.InNamedScope(namedScope);
kernel.Bind<Presenter>().ToSelf()
.WithCreatorAsConstructorArgument("view");
// Act
var view = kernel.Get<View>();
kernel.Release(view);
}
}
public class View
{
public View()
{
}
public View(ViewModel vm)
{
ViewModel = vm;
}
public ViewModel ViewModel { get; set; }
}
public class ViewModel
{
}
public class Presenter
{
public View View { get; set; }
public ViewModel ViewModel { get; set; }
public Presenter(View view, ViewModel viewModel)
{
View = view;
ViewModel = viewModel;
}
}
}
The dotMemory.Check assert fails and when analyzing the snapshot the ViewModel has references to the Ninject cache. I thought the named scope should be released when the View was released.
Regards, Andreas
TL;DR
short answer: add
INotifyWhenDisposed
to yourView
. Dispose the view. This will lead to ninject automatically disposing all stuff boundInNamedScope
plus also ninject will un-reference these objects. This will lead to (eventual) garbage collection (unless you're hanging on to strong references elsewhere).Why your implementation doesn't work
Ninject does not get informed when the view is released / get's disposed. That's why ninject has a timer running to check whether the scope-object is still alive (alive = not garbage collected). If the scope-object is not alive anymore it disposes/releases all the objects which were held in scope.
I believe the timer is set to 30seconds by default.
Now what does this mean exactly?
Deterministically Releasing Scoped Objects
Now if you need the objects to be disposed/released immediately when the scope is released, you will need to add
INotifyWhenDisposed
to the scope object (also see here). With named scopes, you'll need to add this interface to the type which is bound withDefinesNamedScope
- in your case theView
.According to Ninject.Extensions.NamedScope's integration tests this will suffice: see here
Note: The only thing which is really made deterministic by this is the disposal of scoped objects. In practice this will usually cut the time for garbage collection to occur significantly, too. However, if there's no memory pressure, again, the actual collection could still take a long time.
Implementing this should get the unit test to pass.
Note: if the root object is bound
InCallScope
then this solution does not work (ninject 3.2.2 / NamedScope 3.2.0). I think it's due to a bug withInCallScope
but sadly i failed to report it (the bug) a few years back. I may just as well be mistaking, though.Proof that implementing
INotifyWhenDisposed
in the root object will dispose childrenIt even works with
DefineDependency
andWithCreatorAsConstructorArgument
I don't have dotMemory.Unit but this checks whether ninject keeps a strong reference to the objects in its cache: