Skip to content

fix: release removed and cleared slots on netstandard builds [patch] - #103

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/79-netstandard-slot-clearing
Oct 6, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/79-netstandard-slot-clearing

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #79

What was wrong

On the netstandard2.0 and netstandard2.1 builds, the slot-clearing guard fell back to !typeof(T).IsValueType. That check is false for every struct, including structs that hold references:

  • ContiguousMap stores a struct Entry, so on those builds it never released a removed or cleared key or value.
  • ContiguousCollection, ContiguousSet and RingBuffer leaked struct elements such as KeyValuePair<int, object> or (string, int).

Change

  • New internal SlotClearing.IsNeeded<T>():
    • It uses RuntimeHelpers.IsReferenceOrContainsReferences<T>() wherever that API exists (netstandard2.1, netcoreapp2.0 and later).
    • On netstandard2.0 it always returns true. Clearing a slot is cheap, and skipping one that holds a reference leaks.
  • All seven guard sites now call it: ContiguousMap Remove/Clear, ContiguousCollection RemoveAt/Clear, ContiguousSet Remove/Clear, and RingBuffer.Clear. The #if NET5_0_OR_GREATER blocks and their conditional using lines are gone.
  • No public API changes. On net5+ the behaviour is unchanged.

Tests

New file: Containers.Test/SlotClearingTests.cs.

  • WeakReference tests: values are collectable after ContiguousMap.Clear, after removing every key from a ContiguousMap, and after Clear/RemoveAt on a ContiguousCollection<KeyValuePair<int, object>> and Clear on a ContiguousSet<KeyValuePair<int, object>>.
  • IsNeeded tests: true for object, KeyValuePair<int, object> and (string, int), and false for int.
  • Containers_DoNotDecideSlotClearingWithAValueTypeCheck: a source guard that fails if any container goes back to an IsValueType check.
    • The test project only targets net10.0, so the behavioural tests can't reach the netstandard fallback. This guard is the in-suite regression check.
    • With the container changes reverted, this test fails and names all four files.

Manual netstandard2.0 check (the issue's acceptance criteria allow this when CI has no netstandard leg). I referenced the built netstandard2.0/ktsu.Containers.dll from a net10 console app and checked with a WeakReference after GC.Collect:

Case Before After
ContiguousMap<int, object>.Clear() alive collected
ContiguousMap<int, object>.Remove(key) alive collected
ContiguousCollection<KeyValuePair<int, object>>.Clear() alive collected

Full suite: 431 tests passed (dotnet test).

There is no overlap with the open RingBuffer PRs #99 and #100. They change PushBack and enumeration, not Clear.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NfxXtpcxX8hkLYrePfHkV4


Generated by Claude Code

On netstandard2.0/2.1 the slot-clearing guard fell back to
!typeof(T).IsValueType, which is false for every struct, including ones that
hold references. ContiguousMap stores a struct Entry, so it never released a
removed or cleared key or value there, and ContiguousCollection, ContiguousSet
and RingBuffer leaked for struct elements such as KeyValuePair<int, object>.

The decision now lives in SlotClearing.IsNeeded<T>(): it uses
RuntimeHelpers.IsReferenceOrContainsReferences<T>() wherever the API exists
(netstandard2.1, netcoreapp2.0 and later) and always clears on
netstandard2.0.

Fixes #79

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NfxXtpcxX8hkLYrePfHkV4
@sonarqubecloud

sonarqubecloud Bot commented Oct 6, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit b1219b4 into main Oct 6, 2026
14 checks passed
@matt-edmondson
matt-edmondson deleted the fix/79-netstandard-slot-clearing branch October 6, 2026 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

On netstandard builds, ContiguousMap (and ContiguousCollection/Set of reference-holding structs) never release removed or cleared items

2 participants