如何解决使用 NamedScope 扩展时如何让 Ninject 停用范围定义对象?
我有一个 Windows 窗体应用程序,我使用 Ninject 和 Program.Main() 入口点作为组合根。我有一个在瞬态范围内创建的 Form1 类型的主要形式。然后该表单创建了一个 Form2 类型的第二个表单,它定义了一个名为“Form2”的命名范围。 Form2 有自己的依赖项,这些依赖项在 Form2 命名范围内绑定了 ToSelf()。
我需要实现的是在关闭 Form2 时停用 Form2 及其依赖项。如果我将 OnDeactivation() 调用添加到 Form2 及其依赖项,则在关闭 Form2 时不会调用 OnDeactivation 操作。仅当我关闭主窗体并退出主 Application.Run() 调用时才调用依赖项,而不是 Form2 本身,此时 Ninject 内核被释放。
本质上,它的行为就好像 Ninject 没有管理 Form2 的生命周期。我无法从文档中判断这是否是预期的行为。
我认为这可能是由于 Windows 在某处保留了对 Form2 的强引用,所以我尝试在表单上调用 Dispose(),并确保我没有在自己的代码中保留任何引用。尽管如此,当我关闭 Form2 时,不会调用 Form2 OnDeactivation 操作。
我也尝试在 Form2 关闭后调用 GC.Collect(),没有任何变化。
我的最终目标是使用 Ninject.Extensions.AppccelerateEventBroker 扩展在 Ninject 激活它们时自动注册 Form2 及其对事件代理的依赖项,并在 Ninject 停用它们时从事件代理中取消注册它们。当我尝试这个时,由于 Ninject 在我关闭 Form2 时没有停用 Form2 及其依赖项,事件代理仍然注册了它们,并且它们仍然接收事件,即使在表单关闭之后,这是我需要解决的主要问题。
那么,如何让 Ninject 在关闭时停用 Form2(定义范围的对象)?
一些重现代码:
using System;
using System.Windows.Forms;
using Ninject;
using Ninject.Extensions.NamedScope;
namespace FormGcTest
{
static class Program
{
[STAThread]
static void Main()
{
using (var container = new StandardKernel()) {
container.Bind<Form1>()
.ToSelf()
.OnActivation(x => Console.WriteLine("Form1 activated"))
.OnDeactivation(x => Console.WriteLine("Form1 deactivated"))
;
container.Bind<Form2>()
.ToSelf()
.OnActivation(x => Console.WriteLine("Form2 activated"))
.OnDeactivation(x => Console.WriteLine("Form2 deactivated"))
.DefinesNamedScope("Form2")
;
container.Bind<Form2Dependency>()
.ToSelf()
.InNamedScope("Form2")
.OnActivation(x => Console.WriteLine("Form2Dependency activated"))
.OnDeactivation(x => Console.WriteLine("Form2Dependency deactivated"))
;
var form1 = container.Get<Form1>();
Application.Run(form1);
}
}
}
public partial class Form1 : Form
{
private readonly Form2 mForm2;
public Form1(Form2 form2)
{
mForm2 = form2;
mForm2.FormClosed += Form2_FormClosed;
InitializeComponent();
}
private void Form2_FormClosed(object sender,FormClosedEventArgs e)
{
mForm2 = null;
}
~Form1()
{
Console.WriteLine("Form1 finalizer");
}
private void BtnGc_Click(object sender,EventArgs e)
{
GC.Collect();
}
private void Form1_Shown(object sender,EventArgs e)
{
mForm2.Show();
}
}
public partial class Form2 : Form
{
private readonly Form2Dependency mForm2Dependency;
public Form2(Form2Dependency form2Dependency)
{
mForm2Dependency = form2Dependency;
InitializeComponent();
FormClosed += Form2_FormClosed;
}
private void Form2_FormClosed(object sender,FormClosedEventArgs e)
{
Dispose();
}
~Form2()
{
Console.WriteLine("Form2 finalizer");
}
private void Form2_Shown(object sender,EventArgs e)
{
mForm2Dependency.DoSomething();
}
}
public class Form2Dependency
{
~Form2Dependency()
{
Console.WriteLine("Form2Dependency finalizer");
}
public void DoSomething()
{
Console.WriteLine("Doing something");
}
}
}
当我运行这个测试应用程序时,Form1 出现,然后 Form2 出现,正如预期的那样。此时,控制台输出显示:
Form2Dependency activated
Form2 activated
Form1 activated
Doing something
然后,如果我关闭 Form2,则没有额外的输出。此时,我期待看到 Ninject 已停用 Form2 和 Form2Dependency。
如果我随后关闭 Form1,并且应用程序退出,我将看到以下额外的控制台输出:
Form2Dependency deactivated
Form2Dependency finalizer
因此您可以看到 Ninject 正在停用 Form2Dependency,而不是 Form2 本身,它仅在应用程序退出时才这样做。
我确定我在这里遗漏了一些东西来让 Ninject 管理 Form2 的生命周期并在我关闭它时让它停用它及其依赖项。我就是想不通我错过了什么。
编辑:在我的问题的原始版本中,我意识到 Form1 在其 mForm2 成员变量中持有对 Form2 注入实例的引用。在此编辑中,我添加了代码让 Form1 订阅 Form2 的 FormClosed 事件,并将该引用设置为 null,这将使 Form2 实例符合垃圾回收条件。即使我在关闭 Form2 后强制运行 GC,Ninject 仍然不会停用 Form2 实例和 Form2Dependency 实例。
编辑 2:通过将 INotifyWhenDisposed 添加到 Form2,我取得了一些进展。现在,当 Form2 关闭时,Ninject 会停用 Form2Dependency。不过,它仍然不会停用 Form2 本身。
编辑 3:我想出了一个可以满足我需求的解决方法,但似乎仍然不是最优雅的解决方案。我正在做的是有一个名为 FormScopeProvider 的单例服务,它有一个 CreateNew() 方法,该方法创建并返回一个 FormScope 实例并将其存储在名为 Current 的公共属性中。 FormScope 是一个非常简单的类,它只实现 INotifyWhenDisposed 接口。然后我在自定义范围内绑定 Form2,使用 FormScopeProvider:
container.Bind<Form2>()
.ToSelf()
.InScope(x => x.Kernel.Get<FormScopeProvider>().CreateNew())
.OnActivation(x => Console.WriteLine($"Form2 {x.GetHashCode()} activated"))
.OnDeactivation(x => Console.WriteLine($"Form2 {x.GetHashCode()} deactivated"))
;
然后在我的主表单中,当我请求一个新的 Form2 实例(我通过注入的工厂完成)时,我从 FormScopeProvider 中获取 Current FormScope 实例,并将其存储在字典中,其中键是 Form2刚刚创建的实例,值是我刚从提供者那里得到的 FormScope 对象。我还订阅了 Form2 的 FormClosed 事件,并在处理程序中从字典中获取关联的 FormScope,并对其调用 Dispose():
private void BtnShowForm2_Click(object sender,EventArgs e)
{
var form2 = mFormFactory.CreateForm2();
mFormScopes.Add(form2,mFormScopeProvider.Current);
form2.FormClosed += Form2_FormClosed;
form2.Show();
}
private void Form2_FormClosed(object sender,FormClosedEventArgs e)
{
var formScope = mFormScopes[sender as Form2];
mFormScopes.Remove(sender as Form2);
formScope.Dispose();
}
这会触发 Ninject 停用该范围内的所有内容,包括 Form2 实例及其 Form2Dependency 实例。
这适用于我的实际应用程序,但我仍然很想知道是否有一种更简洁的方法让 Ninject 在表单定义了命名范围时立即停用表单。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。