Skip to content

Perf: Replace ContainsKey + indexer with TryGetValue to eliminate redundant hash lookups#367

Open
PaulAndersonS wants to merge 2 commits into
mainfrom
paulandersons/perf-dictionary-trygetvalue-calendar
Open

Perf: Replace ContainsKey + indexer with TryGetValue to eliminate redundant hash lookups#367
PaulAndersonS wants to merge 2 commits into
mainfrom
paulandersons/perf-dictionary-trygetvalue-calendar

Conversation

@PaulAndersonS
Copy link
Copy Markdown
Collaborator

Root Cause of the Issue

Multiple locations use the Dictionary.ContainsKey() + dictionary[key] anti-pattern, which computes the hash and locates the bucket twice for every lookup. In hot paths (Calendar navigation, WindowOverlay positioning), this adds unnecessary overhead.

Description of Change

Replaced all ContainsKey + indexer patterns with single TryGetValue() calls, which perform the lookup once and return both existence and value in a single operation.

Files changed:

  • maui/src/Calendar/LoopingPannel/CustomSnapManager.cs — Called during month/year view navigation (hot path)
  • maui/src/Core/WindowOverlay/WindowOverlay.iOS.cs — Called during overlay child positioning
  • maui/src/Core/WindowOverlay/WindowOverlay.Windows.cs — Called during overlay child positioning
  • maui/src/Core/WindowOverlay/WindowOverlay.Android.cs — Called during overlay child positioning

Why This Matters

  • Zero behavior change — purely mechanical refactor
  • Reduces hash computations by 50% at each call site
  • Calendar control: AddChildren() is called on every view navigation; the dictionary lookups happen per visible date group
  • WindowOverlay: Positioning methods are called during layout passes on all platforms

Additional Performance Opportunities Identified

# Issue Location Impact Effort
1 ✅ ContainsKey + indexer → TryGetValue Calendar, Core/WindowOverlay High Low
2 LINQ .Where().Min/Max() in hot paths Charts/ErrorBarSegment, AreaSegment High Medium
3 Items.ToList().FindAll() unnecessary copy Accordion/SfAccordion High Low
4 Cast<T>().ToList().AsReadOnly() in GetVisualChildren Core/SfView High Low
5 .Where().ToList() for Min/Max calculations SparkCharts High Low
6 Multiple array passes for Min/Max Charts/AreaSegment Medium Low
7 new PathF() allocation every draw frame Charts/AreaSegment Medium Medium
8 Collection re-allocation in layout passes Charts/AreaSegment Medium Medium
9 .Cast<T>() in loops vs direct indexing Charts/FastLineSeries Medium Medium
10 Missing ConfigureAwait(false) in library async Multiple Low Medium

Screenshots

N/A — no visual changes (performance-only improvement)

PaulAndersonS and others added 2 commits May 25, 2026 17:26
Eliminates redundant dictionary hash lookups by replacing the
ContainsKey() + indexer[] anti-pattern with single TryGetValue() calls.

Files changed:
- Calendar/LoopingPannel/CustomSnapManager.cs (hot path during navigation)
- Core/WindowOverlay/WindowOverlay.iOS.cs
- Core/WindowOverlay/WindowOverlay.Windows.cs
- Core/WindowOverlay/WindowOverlay.Android.cs

Each occurrence previously computed the hash twice (once to check existence,
once to retrieve the value). TryGetValue does both in a single lookup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace Items.ToList().FindAll() with Items.Where().ToList() and
Items.ToList().FirstOrDefault() with Items.FirstOrDefault().

The original code copied the entire ObservableCollection into a new List
before filtering. Since ObservableCollection<T> already implements
IEnumerable<T>, LINQ can operate on it directly without the intermediate
full copy.

Files changed:
- Accordion/SfAccordion.cs (3 occurrences)
- Accordion/AccordionItemView.cs (1 occurrence)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant