Found by: the issue #201 oracle sweep, docs/notes/oracle-sweep-2026-07-10.md; catalogued as docs/notes/field-notes-patterns.md entry 19 (extends entry 9 / IsNoOpDisposeWrapper, oracle-known-fps.md root-cause 2).
The pattern
internal class Enumerator : IEnumerator<Point> {
...
public void Dispose() { } // literally empty — no base call, no field release
}
// caller:
var enumerator = new Enumerator(this, area);
while (enumerator.MoveNext()) { ... } // never disposed — flagged OWN001
Enumerator implements IDisposable only because IEnumerator<T> requires it, not because it holds a real resource — its Dispose() does nothing at all. Never calling it cannot leak anything.
The existing IsNoOpDisposeWrapper exemption already covers this idea for a handful of named BCL types (StringWriter/StringReader — see docs/notes/no-op-dispose-wrapper.md), but doesn't reach a user-defined type, however trivially empty its Dispose() is.
Evidence — ClosedXML/ClosedXML, 5 confirmed instances, single root cause
ClosedXML/Excel/Cells/Slice.cs:91,109,149,188,219 — all Slice.Enumerator locals (Slice.cs:324-416; Dispose() body verified empty, no other override in the type, no unmanaged handle anywhere in the class)
Suggested direction (not prescriptive)
Rather than (only) growing a list of known-safe type names, recognise any Dispose() method whose body is provably empty (no statements, no base call needed since the type has no base Dispose) as safe to skip — first-party or third-party, named allowlist or not. Keep the existing named-BCL-type path for cases where the body isn't literally empty but is still knowably a no-op (e.g. StringWriter's Dispose calls into TextWriter.Dispose which does real but harmless work).
Scope
No analyzer code changed as part of the sweep — this issue tracks the fix as its own unit of work.
Found by: the issue #201 oracle sweep,
docs/notes/oracle-sweep-2026-07-10.md; catalogued asdocs/notes/field-notes-patterns.mdentry 19 (extends entry 9 /IsNoOpDisposeWrapper,oracle-known-fps.mdroot-cause 2).The pattern
EnumeratorimplementsIDisposableonly becauseIEnumerator<T>requires it, not because it holds a real resource — itsDispose()does nothing at all. Never calling it cannot leak anything.The existing
IsNoOpDisposeWrapperexemption already covers this idea for a handful of named BCL types (StringWriter/StringReader— seedocs/notes/no-op-dispose-wrapper.md), but doesn't reach a user-defined type, however trivially empty itsDispose()is.Evidence — ClosedXML/ClosedXML, 5 confirmed instances, single root cause
ClosedXML/Excel/Cells/Slice.cs:91,109,149,188,219— allSlice.Enumeratorlocals (Slice.cs:324-416;Dispose()body verified empty, no other override in the type, no unmanaged handle anywhere in the class)Suggested direction (not prescriptive)
Rather than (only) growing a list of known-safe type names, recognise any
Dispose()method whose body is provably empty (no statements, no base call needed since the type has no baseDispose) as safe to skip — first-party or third-party, named allowlist or not. Keep the existing named-BCL-type path for cases where the body isn't literally empty but is still knowably a no-op (e.g.StringWriter'sDisposecalls intoTextWriter.Disposewhich does real but harmless work).Scope
No analyzer code changed as part of the sweep — this issue tracks the fix as its own unit of work.