Summary
NetOffice stores wrappers in a global strong object list and removes them only during explicit disposal. If callers forget Dispose() or DisposeAllCOMProxies(), wrappers and their underlying RCWs stay alive indefinitely. There is no finalizer fallback.
Background
Core.AddObjectToList() stores each wrapper in _globalObjectList. Core.DisposeAllCOMProxies() walks that list and disposes wrappers. Individual wrappers call Factory.RemoveObjectFromList() only as part of Dispose().
Relevant code paths:
Source/NetOffice/Core.cs: _globalObjectList
Source/NetOffice/Core.cs: AddObjectToList()
Source/NetOffice/Core.cs: RemoveObjectFromList()
Source/NetOffice/Core.cs: DisposeAllCOMProxies()
Source/NetOffice/COMObject.cs: constructors and Dispose()
Source/NetOffice/COMDynamicObject.cs: constructors and Dispose()
Use Case
A consumer creates temporary Office child objects in loops or event handlers and misses a dispose call on one path. In normal .NET code, losing all managed references would allow the GC to collect the wrapper and eventually let the RCW be released. NetOffice's global strong list prevents that.
Example shape:
void InspectWorkbook(NetOffice.ExcelApi.Application excel)
{
var workbook = excel.ActiveWorkbook;
var sheet = workbook.ActiveSheet;
// missed Dispose() on an exception or early return path
}
Problem
Because the global list keeps strong references, missed dispose calls become permanent lifetime leaks for the Core lifetime. This can keep Office processes alive, retain COM server resources, and make cleanup depend on caller discipline rather than runtime reachability.
Suggested Fix
- Reconsider whether the global object list should hold strong references to every wrapper.
- Consider weak references for monitoring plus explicit ownership for deterministic cleanup.
- Add diagnostics for wrappers that remain undisposed at
DisposeAllCOMProxies() or application shutdown.
- Document that NetOffice requires explicit disposal for every wrapper or root-level cleanup.
Summary
NetOffice stores wrappers in a global strong object list and removes them only during explicit disposal. If callers forget
Dispose()orDisposeAllCOMProxies(), wrappers and their underlying RCWs stay alive indefinitely. There is no finalizer fallback.Background
Core.AddObjectToList()stores each wrapper in_globalObjectList.Core.DisposeAllCOMProxies()walks that list and disposes wrappers. Individual wrappers callFactory.RemoveObjectFromList()only as part ofDispose().Relevant code paths:
Source/NetOffice/Core.cs:_globalObjectListSource/NetOffice/Core.cs:AddObjectToList()Source/NetOffice/Core.cs:RemoveObjectFromList()Source/NetOffice/Core.cs:DisposeAllCOMProxies()Source/NetOffice/COMObject.cs: constructors andDispose()Source/NetOffice/COMDynamicObject.cs: constructors andDispose()Use Case
A consumer creates temporary Office child objects in loops or event handlers and misses a dispose call on one path. In normal .NET code, losing all managed references would allow the GC to collect the wrapper and eventually let the RCW be released. NetOffice's global strong list prevents that.
Example shape:
Problem
Because the global list keeps strong references, missed dispose calls become permanent lifetime leaks for the
Corelifetime. This can keep Office processes alive, retain COM server resources, and make cleanup depend on caller discipline rather than runtime reachability.Suggested Fix
DisposeAllCOMProxies()or application shutdown.