diff --git a/DictionaryEntry.sln b/DictionaryEntry.sln
deleted file mode 100644
index 74945c8..0000000
--- a/DictionaryEntry.sln
+++ /dev/null
@@ -1,34 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31903.59
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DictionaryEntry", "src\DictionaryEntry\DictionaryEntry.csproj", "{6D68E402-5369-4881-B0B3-6C4C8C90C601}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DictionaryEntry.Benchmarks", "src\DictionaryEntry.Benchmarks\DictionaryEntry.Benchmarks.csproj", "{90CC74A9-2AE1-4DE6-8756-126CE0159DD0}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DictionaryEntry.Tests", "src\DictionaryEntry.Tests\DictionaryEntry.Tests.csproj", "{AF00BCB7-06E3-41BA-8312-00742EE9F5E6}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {6D68E402-5369-4881-B0B3-6C4C8C90C601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6D68E402-5369-4881-B0B3-6C4C8C90C601}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6D68E402-5369-4881-B0B3-6C4C8C90C601}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6D68E402-5369-4881-B0B3-6C4C8C90C601}.Release|Any CPU.Build.0 = Release|Any CPU
- {90CC74A9-2AE1-4DE6-8756-126CE0159DD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {90CC74A9-2AE1-4DE6-8756-126CE0159DD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {90CC74A9-2AE1-4DE6-8756-126CE0159DD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {90CC74A9-2AE1-4DE6-8756-126CE0159DD0}.Release|Any CPU.Build.0 = Release|Any CPU
- {AF00BCB7-06E3-41BA-8312-00742EE9F5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AF00BCB7-06E3-41BA-8312-00742EE9F5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AF00BCB7-06E3-41BA-8312-00742EE9F5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AF00BCB7-06E3-41BA-8312-00742EE9F5E6}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
-EndGlobal
diff --git a/DictionaryEntry.slnx b/DictionaryEntry.slnx
new file mode 100644
index 0000000..ff7c289
--- /dev/null
+++ b/DictionaryEntry.slnx
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/DictionaryEntry.Benchmarks/BatchOperationsBenchmarks.cs b/src/DictionaryEntry.Benchmarks/AdvancedOps/BatchOperationsBenchmarks.cs
similarity index 85%
rename from src/DictionaryEntry.Benchmarks/BatchOperationsBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/AdvancedOps/BatchOperationsBenchmarks.cs
index 34e79c8..e4c8130 100644
--- a/src/DictionaryEntry.Benchmarks/BatchOperationsBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/AdvancedOps/BatchOperationsBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.AdvancedOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("BatchOperations")]
-public class BatchOperationsBenchmarks
+[BenchmarkCategory("AdvancedOps")]
+public class BatchOperationsBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
@@ -45,18 +43,17 @@ private void BatchOperationsEntry(string key)
value = Math.Min(100, value);
occupied.Insert(value);
},
- vacant => vacant.Insert(5)
- );
+ vacant => vacant.Insert(5));
}
[Benchmark(Baseline = true)]
public void BatchOperations_Traditional_Exists() => BatchOperationsTraditional(ExistingKey);
[Benchmark]
- public void BatchOperations_Entry_Exists() => BatchOperationsEntry(ExistingKey);
+ public void BatchOperations_Traditional_NotExists() => BatchOperationsTraditional(NewKey);
[Benchmark]
- public void BatchOperations_Traditional_NotExists() => BatchOperationsTraditional(NewKey);
+ public void BatchOperations_Entry_Exists() => BatchOperationsEntry(ExistingKey);
[Benchmark]
public void BatchOperations_Entry_NotExists() => BatchOperationsEntry(NewKey);
diff --git a/src/DictionaryEntry.Benchmarks/PatternMatchingBenchmarks.cs b/src/DictionaryEntry.Benchmarks/AdvancedOps/PatternMatchingBenchmarks.cs
similarity index 77%
rename from src/DictionaryEntry.Benchmarks/PatternMatchingBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/AdvancedOps/PatternMatchingBenchmarks.cs
index e01bf0a..6410878 100644
--- a/src/DictionaryEntry.Benchmarks/PatternMatchingBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/AdvancedOps/PatternMatchingBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.AdvancedOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("PatternMatching")]
-public class PatternMatchingBenchmarks
+[BenchmarkCategory("AdvancedOps")]
+public class PatternMatchingBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
@@ -30,25 +28,22 @@ private string PatternMatchingTraditional(string key)
_ => "Zero or negative"
};
}
+
return "Not found";
}
private string PatternMatchingEntry(string key)
{
return _dictionary.Entry(key).Match(
- occupied =>
+ occupied => occupied.Value() switch
{
- return occupied.Value() switch
- {
- > 100 => "Very large",
- > 50 => "Large",
- > 10 => "Medium",
- > 0 => "Small",
- _ => "Zero or negative"
- };
+ > 100 => "Very large",
+ > 50 => "Large",
+ > 10 => "Medium",
+ > 0 => "Small",
+ _ => "Zero or negative"
},
- _ => "Not found"
- );
+ _ => "Not found");
}
private void DifferentActionsTraditional(string key)
@@ -67,8 +62,7 @@ private void DifferentActionsEntry(string key)
{
_dictionary.Entry(key).Match(
occupied => occupied.Insert(occupied.Value() * 2),
- vacant => vacant.Insert(1)
- );
+ vacant => vacant.Insert(1));
}
[Benchmark(Baseline = true)]
diff --git a/src/DictionaryEntry.Benchmarks/DefaultValueBenchmarks.cs b/src/DictionaryEntry.Benchmarks/BasicOps/DefaultValueBenchmarks.cs
similarity index 87%
rename from src/DictionaryEntry.Benchmarks/DefaultValueBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/BasicOps/DefaultValueBenchmarks.cs
index 4baee51..7f5a9db 100644
--- a/src/DictionaryEntry.Benchmarks/DefaultValueBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/BasicOps/DefaultValueBenchmarks.cs
@@ -1,12 +1,9 @@
-using BenchmarkDotNet.Attributes;
-// ReSharper disable PreferConcreteValueOverDefault
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.BasicOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("DefaultValue")]
-public class DefaultValueBenchmarks
+[BenchmarkCategory("BasicOps")]
+public class DefaultValueBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private Dictionary _stringDictionary = null!;
@@ -28,6 +25,7 @@ private int DefaultValueTraditional(string key)
value = default;
_dictionary[key] = value;
}
+
return value;
}
@@ -38,6 +36,7 @@ private int DefaultValueTraditional(string key)
value = default;
_stringDictionary[key] = value;
}
+
return value;
}
diff --git a/src/DictionaryEntry.Benchmarks/BasicOps/DictionaryContainsBenchmarks.cs b/src/DictionaryEntry.Benchmarks/BasicOps/DictionaryContainsBenchmarks.cs
new file mode 100644
index 0000000..28c519f
--- /dev/null
+++ b/src/DictionaryEntry.Benchmarks/BasicOps/DictionaryContainsBenchmarks.cs
@@ -0,0 +1,29 @@
+using BenchmarkDotNet.Attributes;
+
+namespace DictionaryEntry.Benchmarks.BasicOps;
+
+[BenchmarkCategory("BasicOps")]
+public class DictionaryContainsBenchmarks : BenchmarkBase
+{
+ private Dictionary _dictionary = null!;
+ private const int ExistingKey = 42;
+ private const int MissingKey = 21;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _dictionary = new Dictionary { { ExistingKey, 1 } };
+ }
+
+ [Benchmark(Baseline = true)]
+ public bool TryGetValue_Found() => _dictionary.TryGetValue(ExistingKey, out _);
+
+ [Benchmark]
+ public bool Entry_IsOccupied_Found() => _dictionary.Entry(ExistingKey).IsOccupied();
+
+ [Benchmark]
+ public bool TryGetValue_NotFound() => _dictionary.TryGetValue(MissingKey, out _);
+
+ [Benchmark]
+ public bool Entry_IsOccupied_NotFound() => _dictionary.Entry(MissingKey).IsOccupied();
+}
diff --git a/src/DictionaryEntry.Benchmarks/GetOrAddBenchmarks.cs b/src/DictionaryEntry.Benchmarks/BasicOps/GetOrAddBenchmarks.cs
similarity index 73%
rename from src/DictionaryEntry.Benchmarks/GetOrAddBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/BasicOps/GetOrAddBenchmarks.cs
index 42ca30c..fb13c30 100644
--- a/src/DictionaryEntry.Benchmarks/GetOrAddBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/BasicOps/GetOrAddBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.BasicOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("GetOrAdd")]
-public class GetOrAddBenchmarks
+[BenchmarkCategory("BasicOps")]
+public class GetOrAddBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const int ExistingKey = 42;
@@ -26,14 +24,14 @@ public void Cleanup()
private int GetOrAddTraditional(int key)
{
- if (_dictionary.TryGetValue(key, out var val))
+ if (_dictionary.TryGetValue(key, out var value))
{
- return val;
+ return value;
}
- val = 1;
- _dictionary[key] = val;
- return val;
+ value = 1;
+ _dictionary[key] = value;
+ return value;
}
private int GetOrAddEntry(int key)
diff --git a/src/DictionaryEntry.Benchmarks/IncrementCounterBenchmarks.cs b/src/DictionaryEntry.Benchmarks/BasicOps/IncrementCounterBenchmarks.cs
similarity index 90%
rename from src/DictionaryEntry.Benchmarks/IncrementCounterBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/BasicOps/IncrementCounterBenchmarks.cs
index d34815a..cd3e85a 100644
--- a/src/DictionaryEntry.Benchmarks/IncrementCounterBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/BasicOps/IncrementCounterBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.BasicOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("IncrementCounter")]
-public class IncrementCounterBenchmarks
+[BenchmarkCategory("BasicOps")]
+public class IncrementCounterBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
diff --git a/src/DictionaryEntry.Benchmarks/InitializeAbsentBenchmarks.cs b/src/DictionaryEntry.Benchmarks/BasicOps/InitializeAbsentBenchmarks.cs
similarity index 83%
rename from src/DictionaryEntry.Benchmarks/InitializeAbsentBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/BasicOps/InitializeAbsentBenchmarks.cs
index ecd2973..593d7f3 100644
--- a/src/DictionaryEntry.Benchmarks/InitializeAbsentBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/BasicOps/InitializeAbsentBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.BasicOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("InitializeAbsent")]
-public class InitializeAbsentBenchmarks
+[BenchmarkCategory("BasicOps")]
+public class InitializeAbsentBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
diff --git a/src/DictionaryEntry.Benchmarks/RetrieveAndRemoveBenchmarks.cs b/src/DictionaryEntry.Benchmarks/BasicOps/RetrieveAndRemoveBenchmarks.cs
similarity index 75%
rename from src/DictionaryEntry.Benchmarks/RetrieveAndRemoveBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/BasicOps/RetrieveAndRemoveBenchmarks.cs
index 1124708..f19adb1 100644
--- a/src/DictionaryEntry.Benchmarks/RetrieveAndRemoveBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/BasicOps/RetrieveAndRemoveBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.BasicOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("GetAndRemove")]
-public class GetAndRemoveBenchmarks
+[BenchmarkCategory("BasicOps")]
+public class RetrieveAndRemoveBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
@@ -19,12 +17,7 @@ public void Setup()
private int? GetAndRemoveTraditional(string key)
{
- if (_dictionary.Remove(key, out var value))
- {
- return value;
- }
-
- return null;
+ return _dictionary.Remove(key, out var value) ? value : null;
}
private int? GetAndRemoveEntry(string key)
diff --git a/src/DictionaryEntry.Benchmarks/BasicOps/TryGetEntryBenchmarks.cs b/src/DictionaryEntry.Benchmarks/BasicOps/TryGetEntryBenchmarks.cs
new file mode 100644
index 0000000..0891e68
--- /dev/null
+++ b/src/DictionaryEntry.Benchmarks/BasicOps/TryGetEntryBenchmarks.cs
@@ -0,0 +1,44 @@
+using BenchmarkDotNet.Attributes;
+
+namespace DictionaryEntry.Benchmarks.BasicOps;
+
+[BenchmarkCategory("BasicOps")]
+public class TryGetEntryBenchmarks : BenchmarkBase
+{
+ private Dictionary _dictionary = null!;
+ private const string ExistingKey = "existing";
+ private const string MissingKey = "missing";
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _dictionary = new Dictionary { { ExistingKey, 42 } };
+ }
+
+ private int? TryLookupTraditional(string key)
+ {
+ return _dictionary.TryGetValue(key, out var value) ? value : null;
+ }
+
+ private int? TryLookupEntry(string key)
+ {
+ if (_dictionary.Entry(key).TryGetOccupied(out var occupied))
+ {
+ return occupied.Value();
+ }
+
+ return null;
+ }
+
+ [Benchmark(Baseline = true)]
+ public int? TryLookup_Traditional_Exists() => TryLookupTraditional(ExistingKey);
+
+ [Benchmark]
+ public int? TryLookup_Traditional_NotExists() => TryLookupTraditional(MissingKey);
+
+ [Benchmark]
+ public int? TryLookup_Entry_Exists() => TryLookupEntry(ExistingKey);
+
+ [Benchmark]
+ public int? TryLookup_Entry_NotExists() => TryLookupEntry(MissingKey);
+}
diff --git a/src/DictionaryEntry.Benchmarks/BenchmarkBase.cs b/src/DictionaryEntry.Benchmarks/BenchmarkBase.cs
new file mode 100644
index 0000000..8abdc02
--- /dev/null
+++ b/src/DictionaryEntry.Benchmarks/BenchmarkBase.cs
@@ -0,0 +1,12 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Engines;
+
+namespace DictionaryEntry.Benchmarks;
+
+[SimpleJob(RunStrategy.Throughput, iterationCount: 15, warmupCount: 10, invocationCount: 100_000_000)]
+[MemoryDiagnoser(displayGenColumns: false)]
+[HideColumns("Error", "StdDev", "Median", "Ratio", "Alloc Ratio", "RatioSD")]
+[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByJob)]
+[CategoriesColumn]
+public abstract class BenchmarkBase;
diff --git a/src/DictionaryEntry.Benchmarks/ConditionalGetOrComputeBenchmarks.cs b/src/DictionaryEntry.Benchmarks/ConditionalOps/ConditionalGetOrComputeBenchmarks.cs
similarity index 72%
rename from src/DictionaryEntry.Benchmarks/ConditionalGetOrComputeBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/ConditionalOps/ConditionalGetOrComputeBenchmarks.cs
index d9b0efb..b051340 100644
--- a/src/DictionaryEntry.Benchmarks/ConditionalGetOrComputeBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/ConditionalOps/ConditionalGetOrComputeBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.ConditionalOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("ConditionalGetOrCompute")]
-public class ConditionalGetOrCompute
+[BenchmarkCategory("ConditionalOps")]
+public class ConditionalGetOrComputeBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
@@ -17,38 +15,38 @@ public void Setup()
_dictionary = new Dictionary { { ExistingKey, 10 } };
}
- private int ComputeValue()
+ private static int ComputeValue()
{
return DateTime.UtcNow.Millisecond % 100;
}
- private int ComputeValueFromString(string key)
+ private static int ComputeValueFromString(string key)
{
return key.Length;
}
private int GetOrComputeTraditional(string key)
{
- if (_dictionary.TryGetValue(key, out var val))
+ if (_dictionary.TryGetValue(key, out var value))
{
- return val;
+ return value;
}
- val = ComputeValue();
- _dictionary[key] = val;
- return val;
+ value = ComputeValue();
+ _dictionary[key] = value;
+ return value;
}
private int GetOrComputeTraditionalUsingKey(string key)
{
- if (_dictionary.TryGetValue(key, out var val))
+ if (_dictionary.TryGetValue(key, out var value))
{
- return val;
+ return value;
}
- val = ComputeValueFromString(key);
- _dictionary[key] = val;
- return val;
+ value = ComputeValueFromString(key);
+ _dictionary[key] = value;
+ return value;
}
private int GetOrComputeEntry(string key)
diff --git a/src/DictionaryEntry.Benchmarks/ConditionalModificationBenchmarks.cs b/src/DictionaryEntry.Benchmarks/ConditionalOps/ConditionalModificationBenchmarks.cs
similarity index 85%
rename from src/DictionaryEntry.Benchmarks/ConditionalModificationBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/ConditionalOps/ConditionalModificationBenchmarks.cs
index 8a3787d..e6bd3a6 100644
--- a/src/DictionaryEntry.Benchmarks/ConditionalModificationBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/ConditionalOps/ConditionalModificationBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.ConditionalOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("ConditionalModification")]
-public class ConditionalModificationBenchmarks
+[BenchmarkCategory("ConditionalOps")]
+public class ConditionalModificationBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
@@ -52,8 +50,7 @@ private void ConditionalModifyEntry(string key)
occupied.Insert(value + 1);
}
},
- vacant => vacant.Insert(1)
- );
+ vacant => vacant.Insert(1));
}
[Benchmark(Baseline = true)]
diff --git a/src/DictionaryEntry.Benchmarks/InsertOrUpdateBenchmarks.cs b/src/DictionaryEntry.Benchmarks/ConditionalOps/InsertOrUpdateBenchmarks.cs
similarity index 75%
rename from src/DictionaryEntry.Benchmarks/InsertOrUpdateBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/ConditionalOps/InsertOrUpdateBenchmarks.cs
index 359ce88..7544397 100644
--- a/src/DictionaryEntry.Benchmarks/InsertOrUpdateBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/ConditionalOps/InsertOrUpdateBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.ConditionalOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("InsertOrUpdate")]
-public class UpdateOrInsertBenchmarks
+[BenchmarkCategory("ConditionalOps")]
+public class InsertOrUpdateBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
@@ -19,10 +17,10 @@ public void Setup()
private void InsertOrUpdateTraditional(string key)
{
- if (_dictionary.TryGetValue(key, out var val))
+ if (_dictionary.TryGetValue(key, out var value))
{
- val *= 2;
- _dictionary[key] = val;
+ value *= 2;
+ _dictionary[key] = value;
}
else
{
diff --git a/src/DictionaryEntry.Benchmarks/UpdateExistingBenchmarks.cs b/src/DictionaryEntry.Benchmarks/ConditionalOps/UpdateExistingBenchmarks.cs
similarity index 72%
rename from src/DictionaryEntry.Benchmarks/UpdateExistingBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/ConditionalOps/UpdateExistingBenchmarks.cs
index 5dc7e84..6c82796 100644
--- a/src/DictionaryEntry.Benchmarks/UpdateExistingBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/ConditionalOps/UpdateExistingBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.ConditionalOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("UpdateExisting")]
-public class UpdateExistingBenchmarks
+[BenchmarkCategory("ConditionalOps")]
+public class UpdateExistingBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
@@ -19,10 +17,10 @@ public void Setup()
private void UpdateTraditional(string key)
{
- if (_dictionary.TryGetValue(key, out var val))
+ if (_dictionary.TryGetValue(key, out var value))
{
- val *= 2;
- _dictionary[key] = val;
+ value *= 2;
+ _dictionary[key] = value;
}
}
diff --git a/src/DictionaryEntry.Benchmarks/UpsertBenchmarks.cs b/src/DictionaryEntry.Benchmarks/ConditionalOps/UpsertBenchmarks.cs
similarity index 81%
rename from src/DictionaryEntry.Benchmarks/UpsertBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/ConditionalOps/UpsertBenchmarks.cs
index f213340..4f87ebf 100644
--- a/src/DictionaryEntry.Benchmarks/UpsertBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/ConditionalOps/UpsertBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.ConditionalOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("Upsert")]
-public class UpsertBenchmarks
+[BenchmarkCategory("ConditionalOps")]
+public class UpsertBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
diff --git a/src/DictionaryEntry.Benchmarks/DictionaryEntry.Benchmarks.csproj b/src/DictionaryEntry.Benchmarks/DictionaryEntry.Benchmarks.csproj
index 4872168..bdbfada 100644
--- a/src/DictionaryEntry.Benchmarks/DictionaryEntry.Benchmarks.csproj
+++ b/src/DictionaryEntry.Benchmarks/DictionaryEntry.Benchmarks.csproj
@@ -1,18 +1,19 @@
-
+
Exe
net10.0
enable
enable
+ true
-
+
-
+
diff --git a/src/DictionaryEntry.Benchmarks/FactoryMethodBenchmarks.cs b/src/DictionaryEntry.Benchmarks/FactoryOps/FactoryMethodBenchmarks.cs
similarity index 87%
rename from src/DictionaryEntry.Benchmarks/FactoryMethodBenchmarks.cs
rename to src/DictionaryEntry.Benchmarks/FactoryOps/FactoryMethodBenchmarks.cs
index 2e0fafe..e24593e 100644
--- a/src/DictionaryEntry.Benchmarks/FactoryMethodBenchmarks.cs
+++ b/src/DictionaryEntry.Benchmarks/FactoryOps/FactoryMethodBenchmarks.cs
@@ -1,11 +1,9 @@
-using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes;
-namespace DictionaryEntry.Benchmarks;
+namespace DictionaryEntry.Benchmarks.FactoryOps;
-[MemoryDiagnoser]
-[SimpleJob(invocationCount: 10_000_000)]
-[BenchmarkCategory("FactoryMethod")]
-public class FactoryMethodBenchmarks
+[BenchmarkCategory("FactoryOps")]
+public class FactoryMethodBenchmarks : BenchmarkBase
{
private Dictionary _dictionary = null!;
private const string ExistingKey = "existing";
@@ -17,12 +15,12 @@ public void Setup()
_dictionary = new Dictionary { { ExistingKey, 10 } };
}
- private int ComputeExpensiveValue()
+ private static int ComputeExpensiveValue()
{
return DateTime.UtcNow.Second + DateTime.UtcNow.Minute;
}
- private int ComputeFromKey(string key)
+ private static int ComputeFromKey(string key)
{
return key.Length * 10;
}
@@ -34,6 +32,7 @@ private int FactoryMethodTraditional(string key)
value = ComputeExpensiveValue();
_dictionary[key] = value;
}
+
return value;
}
@@ -44,6 +43,7 @@ private int KeyBasedFactoryTraditional(string key)
value = ComputeFromKey(key);
_dictionary[key] = value;
}
+
return value;
}
diff --git a/src/DictionaryEntry.Benchmarks/Program.cs b/src/DictionaryEntry.Benchmarks/Program.cs
index 3c5f727..9faf0f4 100644
--- a/src/DictionaryEntry.Benchmarks/Program.cs
+++ b/src/DictionaryEntry.Benchmarks/Program.cs
@@ -1,4 +1,14 @@
-using System.Reflection;
using BenchmarkDotNet.Running;
-BenchmarkSwitcher.FromAssembly(Assembly.Load("DictionaryEntry.Benchmarks")).Run(args);
+namespace DictionaryEntry.Benchmarks;
+
+public static class Program
+{
+ public static void Main(string[] args)
+ {
+ BenchmarkSwitcher
+ .FromAssembly(typeof(Program).Assembly)
+ .Run(args);
+ }
+}
+
diff --git a/src/DictionaryEntry.Benchmarks/Properties/launchSettings.json b/src/DictionaryEntry.Benchmarks/Properties/launchSettings.json
deleted file mode 100644
index 94df982..0000000
--- a/src/DictionaryEntry.Benchmarks/Properties/launchSettings.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "profiles": {
- "Benchmarks": {
- "commandName": "Project",
- "commandLineArgs": "--join"
- },
- "Benchmarks-Long": {
- "commandName": "Project",
- "commandLineArgs": "--filter * --job long --join"
- },
- "Benchmarks-Short": {
- "commandName": "Project",
- "commandLineArgs": "--filter * --job short --join"
- }
- }
-}
diff --git a/src/DictionaryEntry.Tests/AdvancedUsage/PatternMatchingTests.cs b/src/DictionaryEntry.Tests/AdvancedUsage/PatternMatchingTests.cs
index e95c0b4..32daaad 100644
--- a/src/DictionaryEntry.Tests/AdvancedUsage/PatternMatchingTests.cs
+++ b/src/DictionaryEntry.Tests/AdvancedUsage/PatternMatchingTests.cs
@@ -38,6 +38,21 @@ public void Match_WithVacantEntry_CallsVacantAction()
Assert.True(vacantCalled);
}
+ [Fact]
+ public void Match_WithVacantEntry_CanInsertThroughVacantAction()
+ {
+ // Arrange
+ var dict = new Dictionary();
+
+ // Act
+ dict.Entry("key").Match(
+ occupied => { },
+ vacant => vacant.Insert(10));
+
+ // Assert
+ Assert.Equal(10, dict["key"]);
+ }
+
[Fact]
public void Match_WithFuncReturnsCorrectValue()
{
diff --git a/src/DictionaryEntry.Tests/BasicUsage/EntryTests.cs b/src/DictionaryEntry.Tests/BasicUsage/EntryTests.cs
index 0238ba0..52e6a0c 100644
--- a/src/DictionaryEntry.Tests/BasicUsage/EntryTests.cs
+++ b/src/DictionaryEntry.Tests/BasicUsage/EntryTests.cs
@@ -70,6 +70,25 @@ public void AndModify_WhenNotExists_DoesNothing()
Assert.True(entry.IsVacant());
}
+ [Fact]
+ public void AndModify_WhenNotExists_DoesNotInvokeCallback()
+ {
+ // Arrange
+ var dict = new Dictionary();
+ var invoked = false;
+
+ // Act
+ dict.Entry("key").AndModify(value =>
+ {
+ invoked = true;
+ return value + 1;
+ });
+
+ // Assert
+ Assert.False(invoked);
+ Assert.Empty(dict);
+ }
+
[Fact]
public void OrInsert_WhenNotExists_InsertsValue()
{
@@ -85,6 +104,20 @@ public void OrInsert_WhenNotExists_InsertsValue()
Assert.Equal(42, value);
}
+ [Fact]
+ public void OrInsert_ReturnsReferenceToInsertedValue()
+ {
+ // Arrange
+ var dict = new Dictionary();
+
+ // Act
+ ref var valueRef = ref dict.Entry("key").OrInsert(42);
+ valueRef = 100;
+
+ // Assert
+ Assert.Equal(100, dict["key"]);
+ }
+
[Fact]
public void OrInsert_WhenExists_DoesNothing()
{
@@ -103,6 +136,23 @@ public void OrInsert_WhenExists_DoesNothing()
Assert.Equal(42, value);
}
+ [Fact]
+ public void OrInsert_WhenExists_ReturnsReferenceToExistingValue()
+ {
+ // Arrange
+ var dict = new Dictionary
+ {
+ ["key"] = 42
+ };
+
+ // Act
+ ref var valueRef = ref dict.Entry("key").OrInsert(43);
+ valueRef = 100;
+
+ // Assert
+ Assert.Equal(100, dict["key"]);
+ }
+
[Fact]
public void OrInsertWith_InsertsValue()
{
@@ -134,18 +184,24 @@ public void OrInsertWithKey_WhenNotExists_InsertsValue()
}
[Fact]
- public void OrInsertWithKey_WhenExists_DoesNothing()
+ public void OrInsertWithKey_WhenExists_DoesNotInvokeFactory()
{
// Arrange
var dict = new Dictionary
{
["key"] = 42
};
+ var factoryCalled = false;
// Act
- var value = dict.Entry("key").OrInsertWithKey(key => key.Length);
+ ref var value = ref dict.Entry("key").OrInsertWithKey(key =>
+ {
+ factoryCalled = true;
+ return key.Length;
+ });
// Assert
+ Assert.False(factoryCalled);
Assert.True(dict.ContainsKey("key"));
Assert.Equal(42, dict["key"]);
Assert.Equal(42, value);
@@ -166,6 +222,20 @@ public void OrDefault_WhenNotExists_InsertsDefaultValue()
Assert.Equal(0, value);
}
+ [Fact]
+ public void OrDefault_ReturnsReferenceAllowingMutation()
+ {
+ // Arrange
+ var dict = new Dictionary();
+
+ // Act
+ ref var valueRef = ref dict.Entry("key").OrDefault();
+ valueRef = 7;
+
+ // Assert
+ Assert.Equal(7, dict["key"]);
+ }
+
[Fact]
public void OrDefault_WhenExists_DoesNothing()
{