Skip to content

Precision gap: WinForms Controls/ToolStripItemCollection membership and IContainer registration not recognized as disposal channels #219

Description

@PhysShell

Found by: the issue #201 oracle sweep, docs/notes/oracle-sweep-2026-07-10.md; catalogued as docs/notes/field-notes-patterns.md entries 12-13.

The patterns

(a) Controls/ToolStripItemCollection membership. A WinForms Control-derived field is added to this.Controls (or a ToolStrip's Items) inside InitializeComponent(); the class never calls .Dispose() on the field directly.

private System.Windows.Forms.Label lblStatus;
lblStatus = new Label();
this.Controls.Add(this.lblStatus);
protected override void Dispose(bool disposing) {
    if (disposing) components?.Dispose();
    base.Dispose(disposing);   // <- Control.Dispose(bool) recursively disposes every Controls child, incl. lblStatus
}

System.Windows.Forms.Control.Dispose(bool) recursively disposes every child in its own Controls collection — that's the framework's own designer-generated contract. Same holds for a ToolStrip/ContextMenuStrip's ToolStripItemCollection.

(b) IContainer-registered component. A component constructed via new T(components) (or explicitly components.Add(x)) is registered with the designer's IContainer; components.Dispose() (the one-liner every WinForms Form/UserControl already has) disposes it.

protected NotifyIcon TrayIcon;
TrayIcon = new NotifyIcon(components);   // registers with the IContainer
// Dispose(bool): components.Dispose() disposes everything registered above

Evidence — ShareX/ShareX, ~30 own-only findings from these two shapes

  • Controls.Add shape (~24 findings, spot-verified): ShareX.HelpersLib/Forms/ImageViewer.cs:396-399 (pbPreview, lblStatus, lblLeft, lblRight), Colors/ColorPicker.cs:84-85 (colorBox, colorSlider), Forms/InputBox.cs:145-147 (btnOK, btnCancel, txtInputText), Forms/ClipboardViewerForm.designer.cs:107-112, and more.
  • IContainer-registration shape (~6 findings, spot-verified): ShareX.HelpersLib/Forms/TrayForm.cs:41 (TrayIcon).

Suggested direction (not prescriptive)

Extend the field-disposal scan's release recognition: a Control field reachable via Controls.Add/AddRange (or a ToolStripItem reachable via .Items.Add/AddRange) whose containing control itself reaches a disposed root is released transitively. Separately, a field constructed via new T(components) or explicitly passed to components.Add(...) is released when components.Dispose() runs.

Scope

No analyzer code changed as part of the sweep — this issue tracks the fix as its own unit of work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions