(这个问题最初是在ninject google组中提出的,但现在我看到stackoverflow似乎更加活跃了。)
我使用namedscopeextension将同一个viewmodel注入到视图和演示者中。在视图被释放之后,内存分析显示视图模型仍然由ninject缓存保留。如何使ninject发布viewmodel?当窗体关闭并释放时,所有的viewmodels都会被释放,但是我正在使用窗体中的工厂创建和删除控件,并且希望viewmodels被垃圾收集到(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;
        }
    }
}

check assert失败,分析快照时viewmodel引用了ninject缓存。我认为当视图被释放时,命名的作用域应该被释放。
当做,
安德烈亚斯

最佳答案

TL;博士
简而言之:将INotifyWhenDisposed添加到您的View中。处理视图。这将导致ninject自动处理绑定InNamedScope的所有内容,并且ninject将取消引用这些对象。这将导致(最终)垃圾回收(除非您在其他地方挂起了强引用)。
为什么你的实现不起作用
当视图被释放/释放时,ninject不会得到通知。
这就是为什么ninject会运行一个计时器来检查scope对象是否仍然存在(alive=not garbage collected)。如果作用域对象不再是活动的,它将处理/释放作用域中保存的所有对象。
我相信计时器默认设置为30秒。
这到底是什么意思?
如果没有内存压力,gc可能需要很长时间,直到scope对象被垃圾收集(或者他可能永远不会这样做)
一旦作用域对象被垃圾收集,处理和释放作用域对象可能需要大约30秒
一旦ninject释放了scope对象,同样,如果没有内存压力,gc可能需要很长时间来收集对象。
确定释放作用域对象
现在,如果需要在释放作用域时立即释放/释放对象,则需要将INotifyWhenDisposed添加到作用域对象(另请参见here)。
对于命名作用域,您需要将此接口添加到与DefinesNamedScope绑定的类型中—在您的情况下是View
根据ninject.extensions.namedscope的集成测试,这就足够了:请参见here
注意:唯一真正确定的是处理作用域对象。
实际上,这通常也会大大缩短垃圾收集的时间。然而,如果没有内存压力,实际的收集仍然可能需要很长时间。
实现这一点可以使单元测试通过。
注意:如果根对象被绑定InCallScope,则此解决方案不起作用(ninject 3.2.2/namedscope 3.2.0)。我认为这是由于一个InCallScope的bug造成的,但遗憾的是,几年前我没有报告这个bug。不过,我也可能弄错了。
证明在根对象中实现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();
    }
}

它甚至可以与DefineDependencyWithCreatorAsConstructorArgument一起工作
我没有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;
        }
    }
}

关于c# - NamedScope和垃圾回收,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32114221/

10-13 08:06