perf(CommunityToolkit.Mvvm): use System.Threading.Lock (NET9+) and FrozenDictionary (NET8+)#1192
Open
Rolling2405 wants to merge 2 commits intoCommunityToolkit:mainfrom
Open
perf(CommunityToolkit.Mvvm): use System.Threading.Lock (NET9+) and FrozenDictionary (NET8+)#1192Rolling2405 wants to merge 2 commits intoCommunityToolkit:mainfrom
Rolling2405 wants to merge 2 commits intoCommunityToolkit:mainfrom
Conversation
Add net10.0 and net10.0-windows10.0.17763.0 to CommunityToolkit.Mvvm's TargetFrameworks, following the existing LTS-only shipping pattern (net8.0 LTS → net10.0 LTS, skipping net9.0 STS). All existing conditional blocks use IsTargetFrameworkCompatible against 'net8.0-windows10.0.17763.0', which returns true for the new net10.0-windows TFM, so Windows-specific code paths apply automatically. NET8_0_OR_GREATER symbols are true for net10.0 as well, so no source changes are required. Also add net10.0 to CommunityToolkit.Mvvm.Roslyn5000.UnitTests, which was the only MVVM runtime test project not yet targeting net10.0. All 408 tests pass on net10.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ionary on NET9+/NET8+ - WeakReferenceMessenger: replace lock(this.recipientsMap) with a dedicated recipientsMapLock field typed as Lock on NET9+, object on older targets. C# 13 automatically uses Lock.EnterScope() for the lock statement, avoiding Monitor.Enter/Exit overhead and enabling a non-blocking TryEnter cleanup path. - StrongReferenceMessenger: same dedicated lock field pattern (6 lock sites). - ConditionalWeakTable2: upgrade lockObject from object to Lock on NET9+. The cross-method lock held across GetEnumerator/Enumerator.Dispose uses lockObject.Enter()/Exit() directly (Lock.Scope is a ref struct and cannot span method boundaries). Monitor.Enter/Exit replaced with #if guards to avoid the CS9216 boxing-of-Lock warning. - ObservableValidator: use FrozenDictionary<string,string> for DisplayNamesMap on NET8+ (write-once via reflection, read-many thereafter). ToFrozenDictionary generates a minimal perfect hash for faster property-name lookups. - CommunityToolkit.Mvvm.csproj: set LangVersion to 13.0 to enable the optimised lock statement for System.Threading.Lock. Other Toolkit projects remain on 12.0; this override mirrors the pattern already used in the Roslyn test projects.
Author
|
@dotnet-policy-service agree |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds two targeted performance improvements to CommunityToolkit.Mvvm for projects targeting .NET 9+ or .NET 8+, guarded by #if NET9_0_OR_GREATER / #if NET8_0_OR_GREATER so all existing TFMs are completely unaffected.
1. \System.Threading.Lock\ for messenger lock fields (NET9+)
Files changed: \WeakReferenceMessenger.cs, \StrongReferenceMessenger.cs, \ConditionalWeakTable2{TKey,TValue}.ZeroAlloc.cs\
Both messengers previously used \lock (this.recipientsMap)\ — locking directly on the data-holding dictionary field. This PR introduces a dedicated
ecipientsMapLock\ field (typed as \System.Threading.Lock\ on NET9+, \object\ on older targets) and redirects all lock sites to use it.
On .NET 9+, the C# 13 \lock\ statement automatically calls \Lock.EnterScope()\ instead of \Monitor.Enter/\Monitor.Exit, which:
\ConditionalWeakTable2\ uses a cross-method lock held across \GetEnumerator()/\Enumerator.Dispose(). Because \Lock.Scope\ is a
ef struct\ it cannot span method boundaries, so this site uses \lockObject.Enter()/\lockObject.Exit()\ directly with #if\ guards (fixing the CS9216 boxing-of-Lock diagnostic).
\CommunityToolkit.Mvvm.csproj\ is updated to \LangVersion=13.0\ to enable the optimised lock statement. This mirrors the pattern already used in the Roslyn test projects (\Roslyn5000.UnitTests\ uses 14.0) and only affects this library project.
2. \FrozenDictionary\ for \ObservableValidator.DisplayNamesMap\ (NET8+)
File changed: \ObservableValidator.cs\
\DisplayNamesMap\ is a \ConditionalWeakTable<Type, Dictionary<string,string>>\ — the inner dictionary is built once per validated type via reflection, then only ever read. On NET8+, this PR changes the inner dictionary type to \FrozenDictionary<string,string>:
Testing
All 408 tests in \CommunityToolkit.Mvvm.Roslyn5000.UnitTests\ pass on
et10.0:
\
Passed! - Failed: 0, Passed: 408, Skipped: 0, Total: 408
\\
The \SourceGenerators.Roslyn5000.UnitTests\ project was intentionally not targeted — it has pre-existing failures on
et10.0\ unrelated to this change (diagnostic text differences due to C# 14 defaults), as noted in PR #1191.
Related
Companion to PR #1191 (net10.0 TFM addition). These improvements are meaningful regardless of that PR but are most impactful when the library is consumed on .NET 9/10.