(这个问题首先在Ninject Google Group中提出,但我现在看到Stackoverflow似乎更活跃了。)
我正在使用NamedScopeExtension将相同的ViewModel注入View和Presenter。发布View后,内存分析显示Ninject缓存仍保留ViewModel。如何让Ninject发布ViewModel?当窗体关闭和处置时,所有ViewModel都会被释放,但我正在使用窗体中的工厂创建和删除控件,并希望将ViewModel垃圾收集到(收集Presenter和View)。
使用dotMemoryUnit查看以下UnitTest,以说明问题:
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;
}
}
}
dotMemory.Check断言失败,在分析快照时,ViewModel引用了Ninject缓存。我认为在View发布时应该发布命名范围。
此致 安德烈亚斯
答案 0 :(得分:6)
简短回答:将INotifyWhenDisposed
添加到View
。处理视图。这将导致ninject自动处理所有绑定的内容InNamedScope
以及ninject将取消引用这些对象。这将导致(最终)垃圾收集(除非您在其他地方依赖强引用)。
当视图被释放/被处置时,Ninject不会得到通知。 这就是为什么ninject有一个运行的计时器来检查范围对象是否仍然存活(alive =不是垃圾收集)的原因。如果scope-object不再存在,它会处置/释放范围内的所有对象。
我认为默认情况下定时器设置为30秒。
现在这究竟意味着什么?
现在,如果您需要在发布作用域时立即释放/释放对象,则需要将INotifyWhenDisposed
添加到作用域对象(另请参阅here)。
对于命名范围,您需要将此接口添加到与DefinesNamedScope
绑定的类型 - 在您的情况下为View
。
根据Ninject.Extensions.NamedScope的集成测试,这就足够了:见here
注意:唯一真正确定的是处理范围对象。 在实践中,这通常也会显着缩短垃圾收集的时间。但是,如果没有内存压力,实际的收集仍然需要很长时间。
实现这个应该让单元测试通过。
注意:如果根对象绑定InCallScope
,则此解决方案不起作用(ninject 3.2.2 / NamedScope 3.2.0)。我认为这是由于InCallScope
的一个错误,但遗憾的是几年前我没有报告它(这个错误)。不过,我可能也会误以为。
INotifyWhenDisposed
将会处置子级public class View : INotifyWhenDisposed
{
public View(ViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler Disposed;
public ViewModel ViewModel { get; private set; }
public bool IsDisposed { get; private set; }
public void Dispose()
{
if (!this.IsDisposed)
{
this.IsDisposed = true;
var handler = this.Disposed;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
}
public class ViewModel : IDisposable
{
public bool IsDisposed { get; private set; }
public void Dispose()
{
this.IsDisposed = true;
}
}
public class IntegrationTest
{
private const string ScopeName = "ViewScope";
[Fact]
public void Foo()
{
var kernel = new StandardKernel();
kernel.Bind<View>().ToSelf()
.DefinesNamedScope(ScopeName);
kernel.Bind<ViewModel>().ToSelf()
.InNamedScope(ScopeName);
var view = kernel.Get<View>();
view.ViewModel.IsDisposed.Should().BeFalse();
view.Dispose();
view.ViewModel.IsDisposed.Should().BeTrue();
}
}
DefineDependency
和WithCreatorAsConstructorArgument
我没有dotMemory.Unit,但这会检查ninject是否保留对其缓存中对象的强引用:
namespace UnitTestProject
{
using FluentAssertions;
using Ninject;
using Ninject.Extensions.DependencyCreation;
using Ninject.Extensions.NamedScope;
using Ninject.Infrastructure.Disposal;
using System;
using Xunit;
public class UnitTest1
{
[Fact]
public void TestMethod()
{
// Arrange
var kernel = new StandardKernel();
const string namedScope = "namedScope";
kernel.Bind<View>().ToSelf()
.DefinesNamedScope(namedScope);
kernel.DefineDependency<View, Presenter>();
kernel.Bind<ViewModel>().ToSelf().InNamedScope(namedScope);
Presenter presenterInstance = null;
kernel.Bind<Presenter>().ToSelf()
.WithCreatorAsConstructorArgument("view")
.OnActivation(x => presenterInstance = x);
var view = kernel.Get<View>();
// named scope should result in presenter and view getting the same view model instance
presenterInstance.Should().NotBeNull();
view.ViewModel.Should().BeSameAs(presenterInstance.ViewModel);
// disposal of named scope root should clear all strong references which ninject maintains in this scope
view.Dispose();
kernel.Release(view.ViewModel).Should().BeFalse();
kernel.Release(view).Should().BeFalse();
kernel.Release(presenterInstance).Should().BeFalse();
kernel.Release(presenterInstance.View).Should().BeFalse();
}
}
public class View : INotifyWhenDisposed
{
public View()
{
}
public View(ViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler Disposed;
public ViewModel ViewModel { get; private set; }
public bool IsDisposed { get; private set; }
public void Dispose()
{
if (!this.IsDisposed)
{
this.IsDisposed = true;
var handler = this.Disposed;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
}
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;
}
}
}