如何修复由ContextMenuStrip引用的对象引起的内存泄漏

本文关键字:内存 泄漏 对象 ContextMenuStrip 引用 何修复 | 更新日期: 2023-09-27 18:12:06

我使用dotMemory来定位内存泄漏。我想要消失的对象是由一个事件处理程序通过ToolStripMenuItem和ContextMenuStrip引用。对象包含以下属性:

    public override ContextMenuStrip PopupMenu
    {
        get
        {
            ContextMenuStrip myPopup = new ContextMenuStrip();
            myPopup.Items.Add(ItemDelete);
            return myPopup;
        }
    }
    public ToolStripMenuItem ItemDelete
    {
        get
        {
            ToolStripMenuItem itemDelete = new ToolStripMenuItem("Delete " + name);
            itemDelete.Enabled = Deletable;
            itemDelete.Image = Properties.Resources.del;
            itemDelete.Click += ItemDelete_Click;
            return itemDelete;
        }
    }

我已经简化了代码,弹出式菜单有大约十几个菜单项,在我使用弹出式菜单删除对象后,它们似乎都抓住了这个对象。我已经尝试重写对象的基delete方法来删除处理程序,但这不起作用。

    public override void delete()
    {
        if (PopupMenu != null)
        {
            ItemDelete.Click -= ItemDelete_Click;
        }
        base.delete();
    }

如何修复由ContextMenuStrip引用的对象引起的内存泄漏

如果没有一个好的,最小的完整的代码示例,就不可能确切地知道问题是什么,更不用说最佳解决方案了。said…

基于你到目前为止发布的少量代码,看来你滥用了c#中的属性特性,并且这样做已经使代码变得足够模糊,以至于你无法识别错误。特别是,你的ItemDelete属性每次调用时都会创建一个新对象,这是实现这种getter的可怕方式。为了使代码正常工作,您必须小心地只调用一次属性getter,并在其他地方手动缓存结果。

在属性getter中创建新对象本身并不坏,但只有当该对象将被属性getter本身缓存并在后续调用中重用时才应该这样做,或者该对象在语义上是一个简单的值(最好是实际值类型,但简单的不可变引用类型也可以)。新创建的对象在功能上与之前创建的对象相同(即它们可以互换使用而不影响代码的正确性)。

鉴于上述情况,可能您会发现以下替代方案是解决问题的有效方法:

private Lazy<ToolStripMenuItem> _itemDelete =
    new Lazy<ToolStripMenuItem>(() => _CreateItemDelete());
private ToolStripMenuItem _CreateItemDelete()
{
    ToolStripMenuItem itemDelete = new ToolStripMenuItem("Delete " + name);
    itemDelete.Enabled = Deletable;
    itemDelete.Image = Properties.Resources.del;
    itemDelete.Click += ItemDelete_Click;
    return itemDelete;
}
public ToolStripMenuItem ItemDelete
{
    get
    {
        return _itemDelete.Value;
    }
}

这将延迟ToolStripMenuItem对象的创建,直到第一次调用属性getter,但将在后续调用getter时返回第一次调用时创建的对象。

通过这种方式,您将确保稍后执行语句ItemDelete.Click -= ItemDelete_Click;时,您实际上是从原始对象中删除事件处理程序,而不是从当时创建的某个新对象中删除事件处理程序。