{"id":29233,"date":"2024-10-14T05:00:00","date_gmt":"2024-10-14T03:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=29233"},"modified":"2025-01-16T15:54:56","modified_gmt":"2025-01-16T14:54:56","slug":"using-structures-as-composite-keys-in-c-dictionaries","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/en\/using-structures-as-composite-keys-in-c-dictionaries\/","title":{"rendered":"Using structures as composite keys in C# dictionaries"},"content":{"rendered":"\n<p>When working with data sets, we often use collections that allow access to data by key. Using a dictionary allows us to get a value from a set with O(1) computational complexity, regardless of how large the collection is. Usually, the key is a simple value type or a string.<\/p>\n\n\n\n<p>The problem arises when we want to use multiple values in the form of a composite key. The usual solution is to create a method that generates a string based on multiple fields and then use the resulting string in dictionaries. Such a procedure is often sufficient for us, but it is not the only option.<\/p>\n\n\n\n<p>In this article, I will introduce the possibilities of using structures as composite keys and what we need to be aware of to avoid performance degradation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Analyzed structure<\/strong><\/h2>\n\n\n\n<p>Let&#8217;s start by presenting the structure under discussion. Our dictionary key will consist of two values, where the first is a string and the second is an integer. Let&#8217;s assume that the string represents the country code while the integer represents the subscription identifier. We will use the unique combination of country code and subscription to determine which supplier provides the services.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic struct SupplierKey\n{\n    public string CountryCode { get; set; }\n \n    public int SubscriptionId { get; set; }\n}\n<\/pre><\/div>\n\n\n<p>Next, we want to retrieve the supplier for a specific user using the following dictionary:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nDictionary&lt;SupplierKey, string&gt; SuppliersMapping { get; set; }\n<\/pre><\/div>\n\n\n<p>The presented dictionary will work, but it will be far from efficient, and we cannot describe the implementation of the structure as correct and following the good practices of the C# language.<\/p>\n\n\n\n<p>Before we move on to examining performance and finding and fixing bugs, we must first discuss the basics of strings, structures, and dictionaries.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Strings<\/strong><\/h2>\n\n\n\n<p>Our structure, which is a value type, contains a string, which is a reference type. As is known, value types are compared by value, while reference types are compared by checking whether the reference points to the same address. Fortunately, strings in C# already have the GetHashCode and Equals methods overridden. This eliminates the need to worry about whether two reference-type objects with the same value are equal.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Equals and GetHashCode for strings<\/strong><\/h2>\n\n\n\n<p>The GetHashCode method goes character by character and performs bitwise operations on the bytes of each character to calculate the final hash code. We can see a similar thing when we analyze the Equals method, where a typical reference check is followed by a calling the EqualsHelper method. Using Span, the helper method iterates over each character from the string and compares its bytes with the bytes of the corresponding character from the other string.<\/p>\n\n\n\n<p>We should also take note of the important comment above the methods we&#8217;ve been discussing (<a href=\"https:\/\/github.com\/dotnet\/runtime\/blob\/332fbb47f0f2fca21873cf2b4260dd8bd32e08f6\/src\/libraries\/System.Private.CoreLib\/src\/System\/String.Comparison.cs#L750\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >System.Private.CoreLib\/src\/System\/String.Comparison.cs<\/a>):<\/p>\n\n\n\n<p><strong><em>If strings A and B are such that A.Equals(B), then they will return the same hash code.<\/em><\/strong><\/p>\n\n\n\n<p>As we will see later when we discuss collections, getting the same hash code for equal objects will be important for us. For now, let&#8217;s note that changing how a type is compared means changing how the hash code is calculated so that two equal instances return the same code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Structures<\/strong><\/h2>\n\n\n\n<p>As we begin discussing structures in .NET, it is important to know that they implicitly derive from the <a href=\"\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">ValueType<\/a> class. This class is the basis that overrides virtual methods from the base Object class, making their implementation more tailored to the needs of value types.<\/p>\n\n\n\n<p>It&#8217;s also worth mentioning that because of this implicit inheritance, structures can&#8217;t inherit from any other class or structure. In addition, the ValueType class is marked as special and can&#8217;t act as a base class for any other class.<\/p>\n\n\n\n<p>When examining how we compare structures and calculate their hash code, we are essentially analyzing methods that come from the ValueType class. The source code for ValueType is available at the link (<a href=\"https:\/\/github.com\/dotnet\/runtime\/blob\/a08fe0fc6cc42cf0764a0e25e31c9d464d060d86\/src\/coreclr\/System.Private.CoreLib\/src\/System\/ValueType.cs#L23\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >System.Private.CoreLib\/src\/System\/ValueType.cs<\/a>) and is not overwhelming at first glance. The file measures just 170 lines of C# code, but when we look closely, we will see that the full code for its methods is not here. We are analyzing C# code that references unmanaged and high-performance CoreCLR code in C++.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Equals in structures<\/strong><\/h2>\n\n\n\n<p>The Equals method has a high-level algorithm defined that references C++ code. The comparison itself begins with a type check. If the types do not match, we immediately know that the instances are not equal. Next, we check if a byte-by-byte comparison is possible. This depends, among other things, on whether all fields are value types. If there is at least one reference type field, we must skip the quick byte check and move on.<\/p>\n\n\n\n<p>There are a few other conditions that can be found in the C++ code (<a href=\"https:\/\/github.com\/dotnet\/runtime\/blob\/3e8b7869620b691f4a47a400ed8a5d15c185d2a2\/src\/coreclr\/vm\/comutilnative.cpp#L1582\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >coreclr\/vm\/comutilnative.cpp<\/a>). One part of the check is whether Equals or GetHashCode has been overloaded. In our analyzed structure, we used a field with a string type, so we need to proceed with a slower comparison.<\/p>\n\n\n\n<p>Further, the values of all fields are extracted using reflection, which is then used for the final comparison of structures. If we look at the parameters and types used, we will see Object everywhere, which means that we will not avoid boxing, also when comparing each of the fields. The combination of boxing and reflection gives us an obvious reason why this method is inefficient for structures.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>GetHashCode in structures<\/strong><\/h2>\n\n\n\n<p>The GetHashCode method is a bit more complex, but, like Equals, it has two main algorithms to choose from, fast and slow. The selection of which is based on the same conditions as before. The fast algorithm calculates the hash code based on bytes, while the slower one is based on a type-specific calculated strategy (<a href=\"https:\/\/github.com\/dotnet\/runtime\/blob\/3e8b7869620b691f4a47a400ed8a5d15c185d2a2\/src\/coreclr\/vm\/comutilnative.cpp#L1677\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >coreclr\/vm\/comutilnative.cpp<\/a>).<\/p>\n\n\n\n<p>Leaving aside the details that are unnecessary for us now, only the first field from the object is selected, and only this field is used to calculate the hash code. This means that for any structure that has at least one field of reference type, the hash code will be the same if the first field has the same value.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>First issue \u2013 without an overridden GetHashCode method, structures only use the first field to calculate the hash code<\/strong><\/h2>\n\n\n\n<p>The information obtained by analyzing the source code of the ValueType class is the basis for repairing the implementation of the SupplierKey structure.<\/p>\n\n\n\n<p>As we found out, SupplierKey, in its current form, uses only the first field (country code) to calculate the hash code. This is confirmed by the code below, where changing the subscription ID does not generate a new hash code.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nvar key1 = new SupplierKey { CountryCode = &quot;DE&quot;, SubscriptionId = 1 };\nvar key2 = new SupplierKey { CountryCode = &quot;DE&quot;, SubscriptionId = 2 };\nvar key3 = new SupplierKey { CountryCode = &quot;PL&quot;, SubscriptionId = 1 };\nvar key4 = new SupplierKey { CountryCode = &quot;PL&quot;, SubscriptionId = 2 };\n \nConsole.WriteLine(key1.GetHashCode()); \/\/ 876931578\nConsole.WriteLine(key2.GetHashCode()); \/\/ 876931578\nConsole.WriteLine(key3.GetHashCode()); \/\/ 1001209387\nConsole.WriteLine(key4.GetHashCode()); \/\/ 1001209387\n\n<\/pre><\/div>\n\n\n<p>What this means for us is that we will be operating on repeated hash codes when using this structure as a key in a dictionary. This is important for performance reasons since the hash code of a key is used to determine which index in the internal array of a dictionary the entry we are inserting will occupy.<\/p>\n\n\n\n<p>If we have multiple objects with the same hash code, we can&#8217;t just use the index to extract values from the dictionary. We also must check the other objects with the same hash code to see which one we want. Thus, our assumed computational complexity O(1) turns into O(n). The code discussed is located inside the Dictionary.FindValue method (<a href=\"https:\/\/github.com\/dotnet\/runtime\/blob\/3916efe1145728ff19f8a04d574c89256df0f762\/src\/libraries\/System.Private.CoreLib\/src\/System\/Collections\/Generic\/Dictionary.cs#L397\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >System.Private.CoreLib\/src\/System\/Collections\/Generic\/Dictionary.cs<\/a>).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Fixing the first issue<\/strong><\/h3>\n\n\n\n<p>We have found a good reason to overload the GetHashCode method. We need to use the remaining fields so that the hash is calculated for the entire key, not just for a part of it. We will use the HashCode.Combine() method for this.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic struct SupplierKey_WithHashCode\n{\n    public string CountryCode { get; set; }\n \n    public int SubscriptionId { get; set; }\n \n    public override int GetHashCode()\n    {\n        return HashCode.Combine(CountryCode, SubscriptionId);\n    }\n}\n<\/pre><\/div>\n\n\n<p>Once we override the GetHashCode method, we get a different hash code every time we change the value of any of the fields in our structure.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nvar key1 = new SupplierKey_WithHashCode { CountryCode = &quot;DE&quot;, SubscriptionId = 1 };\nvar key2 = new SupplierKey_WithHashCode { CountryCode = &quot;DE&quot;, SubscriptionId = 2 };\nvar key3 = new SupplierKey_WithHashCode { CountryCode = &quot;PL&quot;, SubscriptionId = 1 };\nvar key4 = new SupplierKey_WithHashCode { CountryCode = &quot;PL&quot;, SubscriptionId = 2 };\n \nConsole.WriteLine(key1.GetHashCode()); \/\/ 92690337\nConsole.WriteLine(key2.GetHashCode()); \/\/ 535339796\nConsole.WriteLine(key3.GetHashCode()); \/\/ -881525390\nConsole.WriteLine(key4.GetHashCode()); \/\/ 195508225\n<\/pre><\/div>\n\n\n<p>It remains to see if we can get the same hash code for the same values. The code below confirms this.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nvar key1 = new SupplierKey_WithHashCode { CountryCode = &quot;DE&quot;, SubscriptionId = 1 };\nvar key2 = new SupplierKey_WithHashCode { CountryCode = &quot;DE&quot;, SubscriptionId = 1 };\n \nConsole.WriteLine(key1.GetHashCode()); \/\/ 1393730686\nConsole.WriteLine(key2.GetHashCode()); \/\/ 1393730686\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>First performance tests<\/strong><\/h3>\n\n\n\n<p>In theory, we have fixed the problem, but let&#8217;s now conduct a first performance test by checking how long it takes to retrieve values from the dictionary. We will use three sets of data constituting the Cartesian product for X countries and Y subscriptions:<\/p>\n\n\n\n<p>Set 1. X = 50, Y = 50, X\u00d7Y = 2 500,<\/p>\n\n\n\n<p>Set 2. X = 100, Y = 100, X\u00d7Y = 10 000,<\/p>\n\n\n\n<p>Set 3. X = 200, Y = 200, X\u00d7Y = 40 000.<\/p>\n\n\n\n<p>We will be using the BenchmarkDotNet library and .NET 8.0.1 to run the tests. The following code shows the DictionaryGetTests class, which includes the following segments:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The Size field determines the size of the collection. Its value will vary depending on which data set is used for testing.<\/li>\n\n\n\n<li>The IterationSetup method is called before each test and is designed to initialize the dictionaries and insert values into them. The code contained in this method will not be taken into account when measuring performance.<\/li>\n\n\n\n<li>The DictionaryGet_WithoutHashCode and DictionaryGet_WithHashCode methods are the tested blocks of code. This is where the iteration happens across all countries and subscriptions to retrieve values from the corresponding dictionary. For DictionaryGet_WithoutHashCode, the key does not have an overridden GetHashCode method, and for DictionaryGet_WithHashCode, we use a key with an overridden GetHashCode method.<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing System.Collections.Generic;\nusing System.Linq;\nusing BenchmarkDotNet.Attributes;\n \n&#x5B;MemoryDiagnoser(true)]\npublic class DictionaryGetTests\n{\n    \/\/ Collection size\n    &#x5B;Params(50, 100, 200)]\n    public int Size;\n \n    \/\/ Tested dictionaries\n    private Dictionary&lt;SupplierKey, string&gt; _suppliersMapping_WithoutHashCode;\n    private Dictionary&lt;SupplierKey_WithHashCode, string&gt; _suppliersMapping_WithHashCode;\n \n    \/\/ Available keys\n    private SupplierKey&#x5B;] _supplierKeys_WithoutHashCode;\n    private SupplierKey_WithHashCode&#x5B;] _supplierKeys_WithHashCode;\n \n    \/\/ The value for each of the keys in the dictionary is invariable\n    private const string Value = &quot;supplier&quot;;\n \n    \/\/ Test data preparation method \n    &#x5B;IterationSetup]\n    public void IterationSetup()\n    {\n        _suppliersMapping_WithoutHashCode = new Dictionary&lt;SupplierKey, string&gt;(Size*Size);\n        _suppliersMapping_WithHashCode = new Dictionary&lt;SupplierKey_WithHashCode, string&gt;(Size*Size);\n \n        for (int code = 0; code &lt; Size; code++)\n        {\n            var countryCode = ((char)code).ToString();\n \n            for (int subscriptionId = 0; subscriptionId &lt; Size; subscriptionId++)\n            {\n                \/\/ We add a key with the same values but a different type to the dictionaries\n\n                _suppliersMapping_WithoutHashCode.Add(new SupplierKey\n                {\n                    CountryCode = countryCode,\n                    SubscriptionId = subscriptionId\n                }, Value);\n \n                _suppliersMapping_WithHashCode.Add(new SupplierKey_WithHashCode\n                {\n                    CountryCode = countryCode,\n                    SubscriptionId = subscriptionId\n                }, Value);            \n            }\n        }\n \n        _supplierKeys_WithoutHashCode = _suppliersMapping_WithoutHashCode.Keys.ToArray();\n        _supplierKeys_WithHashCode = _suppliersMapping_WithHashCode.Keys.ToArray();    \n    }\n \n    \/\/ Test performed for a dictionary based on keys in an unaltered structure\n    &#x5B;Benchmark]\n    public void DictionaryGet_WithoutHashCode()\n    {\n        foreach (var key in _supplierKeys_WithoutHashCode)\n        {\n            _suppliersMapping_WithoutHashCode.TryGetValue(key, out _);\n        }\n    }\n\n     \/\/ A test performed for a dictionary based on keys in the form of a structure with the GetHashCode method overridden\n    &#x5B;Benchmark]\n    public void DictionaryGet_WithHashCode()\n    {\n        foreach (var key in _supplierKeys_WithHashCode)\n        {\n            _suppliersMapping_WithHashCode.TryGetValue(key, out _);\n        }\n    }\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Results of the first performance tests<\/strong><\/h3>\n\n\n\n<p>After calling the above code, we obtained the results shown in Fig. 1.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image1-2.png\"><img decoding=\"async\" width=\"871\" height=\"158\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image1-2.png\" alt=\"Wyniki pierwszych test\u00f3w wydajno\u015bci\" class=\"wp-image-29224\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image1-2.png 871w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image1-2-300x54.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image1-2-768x139.png 768w\" sizes=\"(max-width: 871px) 100vw, 871px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 1 Results of the first performance tests<\/figcaption><\/figure>\n\n\n\n<p>We can immediately draw the following conclusions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>By overriding the GetHashCode method, we were able to reduce the time required to execute the tested code block<\/li>\n\n\n\n<li>The execution time of DictionaryGet_WithHashCode increases in a nearly linear manner. For 40,000 operations, it represents 4 times the time required to perform 10,000 operations, whereas, for DictionaryGet_WithoutHashCode, it is approximately 7 times more.<\/li>\n\n\n\n<li>For both DictionaryGet_WithHashCode and DictionaryGet_WithoutHashCode, we see many bytes allocated on the heap, which rises as the number of operations performed increases.<\/li>\n<\/ul>\n\n\n\n<p>After a quick analysis of the results, we see that we have achieved the desired result, but there is still room for improvement. Most noticeable is the amount of allocated memory. This constitutes the next problem we will be solving.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Second issue \u2013 unnecessary memory allocations<\/strong><\/h2>\n\n\n\n<p>To understand the issue related to allocations, we need to look again at the source code of dictionaries and, more specifically, at the way they are compared to each other.<\/p>\n\n\n\n<p>The Dictionary.FindValue method is responsible for finding the key in the dictionary, which searches the internal array of the dictionary and tries to return the value corresponding to the given key. The code below shows a condition that comes from Dictionary.FindValue (<a href=\"https:\/\/github.com\/dotnet\/runtime\/blob\/332fbb47f0f2fca21873cf2b4260dd8bd32e08f6\/src\/libraries\/System.Private.CoreLib\/src\/System\/Collections\/Generic\/Dictionary.cs#L397\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >System.Private.CoreLib\/src\/System\/Collections\/Generic\/Dictionary.cs<\/a>).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/ Code called for value types without a defined comparer\nif (typeof(TKey).IsValueType &amp;&amp; comparer == null)\n{\n    \/\/ code omitted for purposes of the article\n\n         \/\/ EqualityComparer&lt;TKey&gt;.Default is used for comparison\n    if (entry.hashCode == hashCode &amp;&amp; EqualityComparer&lt;TKey&gt;.Default.Equals(entry.key, key))\n    {\n        goto ReturnFound;\n    }\n\n    \/\/ code omitted for purposes of the article\n} \n<\/pre><\/div>\n\n\n<p>As we can see, the default EqualityComparer is used for value types, but to understand what this means, we need to move on to the CreateDefaultEqualityComparer method, which is called from EqualityComparer&lt;TKey&gt;.Default (<a href=\"https:\/\/github.com\/dotnet\/runtime\/blob\/7d23d6116e4f8fa9be7b68caec8b977935722404\/src\/coreclr\/System.Private.CoreLib\/src\/System\/Collections\/Generic\/ComparerHelpers.cs#L62\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >System.Private.CoreLib\/src\/System\/Collections\/Generic\/ComparerHelpers.cs<\/a>).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\ninternal static object CreateDefaultEqualityComparer(Type type)\n{\n    \/\/ code omitted for purposes of the article\n\n    \/\/ Condition to check if the dictionary key implements IEquatable&lt;&gt;\n    else if (type.IsAssignableTo(typeof(IEquatable&lt;&gt;).MakeGenericType(type)))\n    {\n        \/\/ An efficient GenericEqualityComparer is returned\n        result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer&lt;string&gt;), runtimeType);\n    }\n\n    \/\/ code omitted for purposes of the article\n\n    \/\/ Inefficient ObjectEqualityComparer is returned\n    return result ?? CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ObjectEqualityComparer&lt;object&gt;), runtimeType);\n}\n<\/pre><\/div>\n\n\n<p>As we can see, if the type of a dictionary key is implementing the IEquatable&lt;&gt; interface, the GenericEqualityComparer based on the IEquatable&lt;&gt; is used. Otherwise, the dictionary will use the slower ObjectEqualityComparer, which must cast our value type onto the object when comparing, thus causing unnecessary memory allocations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Fixing the second issue<\/strong><\/h3>\n\n\n\n<p>Now that we know what causes additional allocations, we can proceed to changes in the code. We need to implement the IEquatable&lt;&gt; interface along with the Equals method. The code below shows the structure of SupplierKey_WithHashCode_AndEquals. Note that we have also overridden the Equals method derived from Object to match the one derived from IEquatable.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic struct SupplierKey_WithHashCode_AndEquals : IEquatable&lt;SupplierKey_WithHashCode_AndEquals&gt;\n{\n    public string CountryCode { get; set; }\n \n    public int SubscriptionId { get; set; }\n \n    \/\/ Equals method derived from base Object\n    public override bool Equals(object? other)\n    {\n        if(other is SupplierKey_WithHashCode_AndEquals key)\n        {\n            return Equals(key);\n        }\n \n        return false;\n    }\n \n    \/\/ Equals method derived from IEquatable&lt;&gt;\n    public bool Equals(SupplierKey_WithHashCode_AndEquals other)\n    {\n        return CountryCode == other.CountryCode &amp;&amp; SubscriptionId == other.SubscriptionId;\n    }\n \n    public override int GetHashCode()\n    {\n        return HashCode.Combine(CountryCode, SubscriptionId);\n    }\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Performance tests after implementing IEquatable&lt;&gt;<\/strong><\/h3>\n\n\n\n<p>After the adjustment, we can move on to the next performance test. The results of which are shown in Fig. 2. The latest changes will be visible in the tests of the DictionaryGet_WithHashCode_AndEquals method, which relies on keys in the form SupplierKey_WithHashCode_AndEquals.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image2-1.png\"><img decoding=\"async\" width=\"1011\" height=\"208\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image2-1.png\" alt=\"Wyniki test\u00f3w wydajno\u015bci po zaimplementowaniu IEquatable<&gt;\" class=\"wp-image-29226\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image2-1.png 1011w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image2-1-300x62.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image2-1-768x158.png 768w\" sizes=\"(max-width: 1011px) 100vw, 1011px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 2 Performance test results after implementing IEquatable&lt;&gt;<\/figcaption><\/figure>\n\n\n\n<p>Let&#8217;s now analyze the achieved times and allocations:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The implementation of the IEquatable interface allowed us to speed up the operation of retrieving values from the dictionary. It now works about 6 times faster while maintaining a linear increase in time relative to the increase in the number of elements in the dictionary.<\/li>\n\n\n\n<li>The number of bytes allocated in memory has dropped significantly. It does not change as the collection size grows. You can see that the BenchmarkDotNet library itself has only allocated 400 bytes (<a href=\"https:\/\/github.com\/dotnet\/BenchmarkDotNet\/issues\/2582\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >you can find more info at the link<\/a>). So, we managed to get rid of the boxing of value types when we were getting values from the dictionary.<\/li>\n<\/ul>\n\n\n\n<p>By making minor changes, <strong>we obtained code that is efficient in terms of the time needed to perform calculations and does not cause additional memory allocations<\/strong>. Now, we can conclude that our structure has been properly implemented.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Comparison of the discussed structure to its counterpart as a record struct<\/strong><\/h2>\n\n\n\n<p>Before the introduction of C# 10, we would have stayed with the structure we described at the end of the previous section, but something has changed. With C# 10 and .NET 6, record struct types were introduced. They already have built-in enhancements that we have sequentially applied to SupplierKey, so we can check how they will behave for our test case.<\/p>\n\n\n\n<p>Let&#8217;s start by defining a new SupplierKey_Record structure, which has the same fields as our existing structure and is marked with the record keyword. In addition, we will use the possibility of defining fields directly in the type declaration, which is presented in the code below.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic record struct SupplierKey_Record(string CountryCode, int SubscriptionId) { }\n<\/pre><\/div>\n\n\n<p>As you can see, the difference in the level of complexity of the code is huge. One line is enough to provide us with everything we had to write by hand.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Performance tests for record struct<\/strong><\/h3>\n\n\n\n<p>Now, we can proceed to performance testing. The method that uses keys in the form of SupplierKey_Record type will be called DictionaryGet_Record. The performance test results for the record struct are shown in Fig. 3.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image3.png\"><img decoding=\"async\" width=\"1014\" height=\"254\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image3.png\" alt=\"Testy wydajno\u015bci dla record struct\" class=\"wp-image-29228\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image3.png 1014w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image3-300x75.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/image3-768x192.png 768w\" sizes=\"(max-width: 1014px) 100vw, 1014px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 3 Performance tests for record struct<\/figcaption><\/figure>\n\n\n\n<p>We can see that the record struct is faster than our structure and we managed to speed up the retrieval of values from the dictionary. Let&#8217;s also note that DictionaryGet_Record, like DictionaryGet_WithHashCode_AndEquals, does not allocate memory and the increase in the number of elements causes a linear increase in execution time.<\/p>\n\n\n\n<p>We could continue to make changes to our GetHashCode and Equals methods to achieve similar results, but we will stop there.<\/p>\n\n\n\n<p>We managed to lead to a situation where <strong>our structure is efficient, does not cause unnecessary allocations and the time needed to retrieve a single value does not change depending on the number of elements in the dictionary.<\/strong> By conducting more accurate measurements using profilers, or trying to choose a better way to calculate the hash code, we could get closer to the record, but this misses our goal.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/sii.pl\/oferty-pracy\/\" target=\"_blank\" rel=\"noreferrer noopener\"><img decoding=\"async\" width=\"737\" height=\"170\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/praca-k-EN.jpg\" alt=\"job offers\" class=\"wp-image-30108\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/praca-k-EN.jpg 737w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/praca-k-EN-300x69.jpg 300w\" sizes=\"(max-width: 737px) 100vw, 737px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Summary<\/strong><\/h2>\n\n\n\n<p>By analyzing a relatively simple case of an ordinary structure with two fields, we managed to understand a large piece of .NET source code. We were able to learn more about the internal implementation of the structures and the reasons why we should follow the generally applicable principles of C# code design. By implementing one interface and overriding the appropriate methods, we achieved a 300x increase in performance.<\/p>\n\n\n\n<p>We also managed to use one of the innovations from recent years in the form of record struct, which allowed us to significantly simplify the code of our structure and achieve even less time needed to retrieve values from the dictionary. This encourages us to keep track of changes made to the new .NET versions and reassures us that the development of the platform is heading in the right direction.<\/p>\n\n\n\n<p>***<\/p>\n\n\n\n<p>If you are interested in C#, also take a look <a href=\"https:\/\/sii.pl\/blog\/en\/search\/C%23\/\" target=\"_blank\" aria-label=\"at other articles by our specialists (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">at other articles by our specialists<\/a>.<\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;29233&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;7&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 7)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Using structures as composite keys in C# dictionaries&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 7)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>When working with data sets, we often use collections that allow access to data by key. Using a dictionary allows &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/en\/using-structures-as-composite-keys-in-c-dictionaries\/\">Continued<\/a><\/p>\n","protected":false},"author":674,"featured_media":29231,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1320],"tags":[2622,1526,1425],"class_list":["post-29233","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-hard-development","tag-digital-en","tag-guidebook","tag-c-en"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/Uzycie-struktur-jako-kluczy-zlozonych-w-slownikach-w-C.jpg","category_names":["Hard development"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/29233"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/users\/674"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/comments?post=29233"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/29233\/revisions"}],"predecessor-version":[{"id":30110,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/29233\/revisions\/30110"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media\/29231"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media?parent=29233"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/categories?post=29233"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/tags?post=29233"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}