From 1248b41ffe6de4196f7f80520ac81f03921bb61c Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Wed, 23 Sep 2026 14:10:40 +0200 Subject: [PATCH] ConcurrentDictionary in C#: retarget net10.0, NUnit 4.6.1, add the unsized naive example - Both projects net7.0 to net10.0. - Microsoft.NET.Test.Sdk 18.10.1, NUnit 4.6.1, NUnit3TestAdapter 6.3.0, NUnit.Analyzers 4.15.0, coverlet.collector 10.0.1. The classic asserts compile unchanged on NUnit 4.6.1, so no test assertion is rewritten. - Add NaiveExampleUnsized, the unsized Dictionary the article opens with, and run it from Program.cs inside a try/catch that prints the exception. No test for it: the throw is probabilistic. - ContentionExample: remove the unused emptyHits local and call the empty-state check directly. - Rename the wall-clock comparison test to end in _Live so CI skips it. - Drop the duplicate sut field initialisers that [SetUp] already assigns. - Program.cs names the two slower examples before running them. --- .../ConcurrentDictionaryDemo.csproj | 2 +- .../ContentionExample.cs | 6 +--- .../NaiveExampleUnsized.cs | 35 +++++++++++++++++++ .../ConcurrentDictionaryDemo/Program.cs | 13 +++++++ .../Tests/ContentionExampleIntegrationTest.cs | 4 +-- .../Tests/MemoryLeakExampleIntegrationTest.cs | 2 +- .../ConcurrentDictionary/Tests/Tests.csproj | 12 +++---- 7 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/NaiveExampleUnsized.cs diff --git a/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/ConcurrentDictionaryDemo.csproj b/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/ConcurrentDictionaryDemo.csproj index 0ce57dc154..d9b084bbd0 100644 --- a/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/ConcurrentDictionaryDemo.csproj +++ b/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/ConcurrentDictionaryDemo.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net10.0 enable enable diff --git a/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/ContentionExample.cs b/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/ContentionExample.cs index befef2cb38..866173bc1e 100644 --- a/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/ContentionExample.cs +++ b/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/ContentionExample.cs @@ -42,15 +42,11 @@ public void RunWithEntryCheck() private void ProcessingStep(int stepNumber) { - int emptyHits = 0; for (int iteration = 0; iteration < MaxIterations; iteration++) { var entryKey = iteration % MaxStateEntries; - if (CheckStateIsEmpty()) - { - emptyHits++; - } + CheckStateIsEmpty(); _sharedState.AddOrUpdate( entryKey, diff --git a/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/NaiveExampleUnsized.cs b/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/NaiveExampleUnsized.cs new file mode 100644 index 0000000000..8c275e72e5 --- /dev/null +++ b/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/NaiveExampleUnsized.cs @@ -0,0 +1,35 @@ +public class NaiveExampleUnsized +{ + public const int MaxIterations = 100; + public const int MaxStateEntries = 10; + public const int ProcessingSteps = 40; + private Dictionary _sharedState = new(); + + public void Run() + { + Parallel.ForEach(Enumerable.Range(0, ProcessingSteps), ProcessingStep); + + PrintState(); + } + + private void ProcessingStep(int stepNumber) + { + for (int iteration = 0; iteration < MaxIterations; iteration++) + { + var entryKey = iteration % MaxStateEntries; + if (!_sharedState.TryGetValue(entryKey, out int entryValue)) + { + entryValue = 0; + } + _sharedState[entryKey] = entryValue + 1; + } + } + + private void PrintState() + { + foreach (var entry in _sharedState) + { + Console.WriteLine($"{entry.Key}\t{entry.Value}"); + } + } +} diff --git a/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/Program.cs b/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/Program.cs index e40c1cee7f..bae93a5fed 100644 --- a/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/Program.cs +++ b/collections-csharp/ConcurrentDictionary/ConcurrentDictionaryDemo/Program.cs @@ -1,4 +1,15 @@  +try +{ + new NaiveExampleUnsized().Run(); +} +catch (AggregateException ex) +{ + Console.WriteLine(ex); +} + +Console.WriteLine(new string('=', 20)); + new NaiveExample().Run(); Console.WriteLine(new string('=', 20)); @@ -15,9 +26,11 @@ Console.WriteLine(new string('=', 20)); +Console.WriteLine("Running ContentionExample, this takes a few seconds..."); new ContentionExample().Run(); Console.WriteLine(new string('=', 20)); +Console.WriteLine("Running MemoryLeakExample, this takes a few seconds..."); new MemoryLeakExample().Run(); diff --git a/collections-csharp/ConcurrentDictionary/Tests/ContentionExampleIntegrationTest.cs b/collections-csharp/ConcurrentDictionary/Tests/ContentionExampleIntegrationTest.cs index c7517c511a..5231652470 100644 --- a/collections-csharp/ConcurrentDictionary/Tests/ContentionExampleIntegrationTest.cs +++ b/collections-csharp/ConcurrentDictionary/Tests/ContentionExampleIntegrationTest.cs @@ -5,7 +5,7 @@ namespace Tests public class ContentionExampleIntegrationTest { private int expectedEntryValue = ContentionExample.ProcessingSteps * ContentionExample.MaxIterations / ContentionExample.MaxStateEntries; - private ContentionExample sut = new(); + private ContentionExample sut; [SetUp] public void SetUp() @@ -30,7 +30,7 @@ public void WhenRunSecondVariant_ThenAllStateEntriesHaveExpectedValue() } [Test] - public void WhenBothVariantsRun_ThenTheSecondVariantTakesMoreTime() + public void WhenBothVariantsRun_ThenTheSecondVariantTakesMoreTime_Live() { ContentionExample firstVariant = new(); ContentionExample secondVariant = new(); diff --git a/collections-csharp/ConcurrentDictionary/Tests/MemoryLeakExampleIntegrationTest.cs b/collections-csharp/ConcurrentDictionary/Tests/MemoryLeakExampleIntegrationTest.cs index 6f3bcb49f2..c34361e86f 100644 --- a/collections-csharp/ConcurrentDictionary/Tests/MemoryLeakExampleIntegrationTest.cs +++ b/collections-csharp/ConcurrentDictionary/Tests/MemoryLeakExampleIntegrationTest.cs @@ -4,7 +4,7 @@ public class MemoryLeakExampleIntegrationTest { private int expectedEntryValue = MemoryLeakExample.ProcessingSteps * MemoryLeakExample.MaxIterations / MemoryLeakExample.MaxStateEntries; - private MemoryLeakExample sut = new(); + private MemoryLeakExample sut; [SetUp] public void SetUp() diff --git a/collections-csharp/ConcurrentDictionary/Tests/Tests.csproj b/collections-csharp/ConcurrentDictionary/Tests/Tests.csproj index 33872f3dc7..c6ccbb5cfa 100644 --- a/collections-csharp/ConcurrentDictionary/Tests/Tests.csproj +++ b/collections-csharp/ConcurrentDictionary/Tests/Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable @@ -9,11 +9,11 @@ - - - - - + + + + +