{"id":29223,"date":"2024-10-14T05:00:00","date_gmt":"2024-10-14T03:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=29223"},"modified":"2025-01-16T15:53:06","modified_gmt":"2025-01-16T14:53:06","slug":"uzycie-struktur-jako-kluczy-zlozonych-w-slownikach-w-c","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/uzycie-struktur-jako-kluczy-zlozonych-w-slownikach-w-c\/","title":{"rendered":"U\u017cycie struktur jako kluczy z\u0142o\u017conych w s\u0142ownikach w C#"},"content":{"rendered":"\n<p>Podczas pracy ze zbiorami danych cz\u0119sto zdarza si\u0119 nam u\u017cy\u0107 kolekcji umo\u017cliwiaj\u0105cych dost\u0119p do danych po kluczu. U\u017cycie s\u0142ownika pozwala otrzyma\u0107 warto\u015b\u0107 ze zbioru przy z\u0142o\u017cono\u015bci obliczeniowej O(1), bez wzgl\u0119du na to, jak du\u017ca jest dana kolekcja. Najcz\u0119\u015bciej klucz stanowi prosty typ warto\u015bciowy lub \u0142a\u0144cuch znak\u00f3w.<\/p>\n\n\n\n<p>Problem pojawia si\u0119, kiedy chcemy u\u017cy\u0107 wielu warto\u015bci, w postaci klucza z\u0142o\u017conego. Zwykle sprowadza si\u0119 to do utworzenia metody generuj\u0105cej \u0142a\u0144cuch znak\u00f3w na podstawie kilku p\u00f3l, a nast\u0119pnie wykorzystywanie powsta\u0142ego \u0142a\u0144cucha w s\u0142ownikach. Taki zabieg cz\u0119sto jest dla nas wystarczaj\u0105cy, ale nie jest to jedyna opcja.<\/p>\n\n\n\n<p>W artykule postaram si\u0119 przybli\u017cy\u0107 mo\u017cliwo\u015bci wykorzystania struktur jako kluczy z\u0142o\u017conych oraz opisz\u0119, na co musimy zwr\u00f3ci\u0107 uwag\u0119, aby nie spowodowa\u0107 spadk\u00f3w wydajno\u015bci.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Analizowana struktura<\/h2>\n\n\n\n<p>Zacznijmy od przedstawienia omawianej struktury. Nasz klucz w s\u0142owniku b\u0119dzie sk\u0142ada\u0107 si\u0119 z dw\u00f3ch warto\u015bci, gdzie pierwsza to \u0142a\u0144cuch znak\u00f3w (string), a druga to liczba ca\u0142kowita (int). Za\u0142\u00f3\u017cmy, \u017ce \u0142a\u0144cuch znak\u00f3w b\u0119dzie reprezentowa\u0107 kod kraju, natomiast liczba ca\u0142kowita b\u0119dzie oznacza\u0107 identyfikator subskrypcji. Z unikalnego po\u0142\u0105czenia kraju oraz subskrypcji uzyskamy informacj\u0119, jaki dostawca b\u0119dzie \u015bwiadczy\u0107 us\u0142ugi.<\/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>Nast\u0119pnie b\u0119dziemy chcieli uzyska\u0107 informacj\u0119 o dostawcy dla konkretnego u\u017cytkownika, korzystaj\u0105c z poni\u017cszego s\u0142ownika:<\/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>Przedstawiony s\u0142ownik b\u0119dzie dzia\u0142a\u0107, ale dzia\u0142anie to dalece odbiega od wydajnego, a implementacj\u0119 struktury nie mo\u017cemy okre\u015bli\u0107 jako poprawn\u0105 i zgodn\u0105 z dobrymi praktykami obowi\u0105zuj\u0105cymi w j\u0119zyku C#.<\/p>\n\n\n\n<p>Zanim przejdziemy do zbadania wydajno\u015bci oraz wskazania i naprawienia b\u0142\u0119d\u00f3w musimy najpierw om\u00f3wi\u0107 podstawowe zagadnienia zwi\u0105zane z \u0142a\u0144cuchami znak\u00f3w, strukturami oraz s\u0142ownikami.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>\u0141a\u0144cuchy znak\u00f3w<\/strong><\/h2>\n\n\n\n<p>Nasza struktura, b\u0119d\u0105c typem warto\u015bciowym, posiada wewn\u0105trz \u0142a\u0144cuch znak\u00f3w, kt\u00f3ry jest typem referencyjnym. Jak wiadomo, typy warto\u015bciowe por\u00f3wnywane s\u0105 przez warto\u015b\u0107, natomiast typy referencyjne por\u00f3wnywane s\u0105 przez sprawdzenie czy referencja wskazuje na ten sam adres. Na szcz\u0119\u015bcie \u0142a\u0144cuchy znak\u00f3w w C# maj\u0105 ju\u017c nadpisane metody GetHashCode oraz Equals. Dzi\u0119ki temu nie musimy si\u0119 przejmowa\u0107 tym, czy dwa obiekty posiadaj\u0105ce t\u0105 sam\u0105 warto\u015b\u0107 b\u0119d\u0105 ze sob\u0105 r\u00f3wne.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Equals i GetHashCode dla \u0142a\u0144cuch\u00f3w znak\u00f3w<\/strong><\/h2>\n\n\n\n<p>Metoda GetHashCode przechodzi znak po znaku i wykonuje na bajtach ka\u017cdego ze znak\u00f3w operacje bitowe s\u0142u\u017c\u0105ce do obliczenia ko\u0144cowego kodu hash. Podobn\u0105 rzecz mo\u017cemy zobaczy\u0107 podczas analizy metody Equals, gdzie po typowym sprawdzeniu referencji nast\u0119puje wywo\u0142anie metody EqualsHelper. Wykorzystuj\u0105c Span, metoda pomocnicza, iteruj\u0105c po ka\u017cdym znaku z ci\u0105gu, por\u00f3wnuje jego bajty z bajtami odpowiadaj\u0105cego znaku z drugiego \u0142a\u0144cucha.<\/p>\n\n\n\n<p>Musimy r\u00f3wnie\u017c zwr\u00f3ci\u0107 uwag\u0119 na wa\u017cny komentarz znajduj\u0105cy si\u0119 nad omawianymi metodami (<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>Jak zobaczymy podczas omawiania kolekcji, uzyskanie takiego samego kodu dla r\u00f3wnych sobie obiekt\u00f3w b\u0119dzie dla nas bardzo wa\u017cne. Na razie zanotujmy, \u017ce zmiana sposobu por\u00f3wnywania danego typu musi wi\u0105za\u0107 si\u0119 zmian\u0105 sposobu obliczania kodu hash, tak aby dwie r\u00f3wne sobie instancje zwraca\u0142y ten sam kod.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Struktury<\/strong><\/h2>\n\n\n\n<p>Rozpoczynaj\u0105c omawianie struktur w .NET, nale\u017cy zaznaczy\u0107, \u017ce stanowi\u0105 one specjalny konstrukt niejawnie dziedzicz\u0105cy z klasy ValueType. Klasa ta stanowi podstaw\u0119, kt\u00f3ra nadpisuje metody wirtualne z bazowej klasy Object, sprawiaj\u0105c, \u017ce ich implementacja jest bardziej dostosowana do potrzeb typ\u00f3w warto\u015bciowych.<\/p>\n\n\n\n<p>Musimy r\u00f3wnie\u017c zauwa\u017cy\u0107, \u017ce przez to niejawne dziedziczenie, struktury nie mog\u0105 dziedziczy\u0107 z \u017cadnej innej klasy b\u0105d\u017a struktury. Dodatkowo sama klasa ValueType oznaczona jest jako specjalna i nie mo\u017ce pe\u0142ni\u0107 roli klasy bazowej dla \u017cadnej innej klasy.<\/p>\n\n\n\n<p>Przygl\u0105daj\u0105c si\u0119 temu, jak dzia\u0142a por\u00f3wnywanie struktur oraz obliczanie ich kodu hash, tak naprawd\u0119 b\u0119dziemy si\u0119 przygl\u0105da\u0107 metodom pochodz\u0105cym z klasy ValueType. Kod \u017ar\u00f3d\u0142owy dla ValueType jest dost\u0119pny pod linkiem (<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>) i nie jest na pierwszy rzut oka przyt\u0142aczaj\u0105cy. Plik mierzy raptem 170 linii kodu C#. Kiedy si\u0119 uwa\u017cnie przyjrzymy, to zobaczymy, \u017ce nie znajduje si\u0119 tu pe\u0142ny kod dla omawianych metod. B\u0119dziemy analizowa\u0107 kod C#, kt\u00f3ry odwo\u0142uje si\u0119 do niezarz\u0105dzanego (ang. Unmanaged) i wysokowydajnego kodu CoreCLR w C++.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Equals w strukturach<\/strong><\/h2>\n\n\n\n<p>Metoda Equals posiada zdefiniowany wysokopoziomowy algorytm, kt\u00f3ry odwo\u0142uje si\u0119 do kodu C++. Samo por\u00f3wnywanie rozpoczynamy od sprawdzenia typ\u00f3w. Je\u015bli si\u0119 nie zgadzaj\u0105, to od razu wiemy, \u017ce nie s\u0105 sobie r\u00f3wne. Nast\u0119pnie sprawdzamy, czy mo\u017cliwe jest por\u00f3wnanie po bajtach. Zale\u017cy to mi\u0119dzy innymi od tego, czy wszystkie pola stanowi\u0105 typy warto\u015bciowe. Je\u015bli wyst\u0119puje co najmniej jedno pole typu referencyjnego, musimy pomin\u0105\u0107 szybkie sprawdzenie bajt\u00f3w i przej\u015b\u0107 dalej.<\/p>\n\n\n\n<p>Istnieje jeszcze kilka innych warunk\u00f3w, kt\u00f3re mo\u017cna znale\u017a\u0107 w kodzie C++ (<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>). Mi\u0119dzy innymi sprawdzane jest to, czy Equals albo GetHashCode nie zosta\u0142y prze\u0142adowane. W naszej analizowanej strukturze u\u017cyli\u015bmy pola o typie string, wi\u0119c musimy przyst\u0105pi\u0107 do wolniejszego por\u00f3wnywania.<\/p>\n\n\n\n<p>Dalej wyci\u0105gane s\u0105 warto\u015bci wszystkich p\u00f3l przy u\u017cyciu refleksji, kt\u00f3re nast\u0119pnie s\u0105 u\u017cyte do ostatecznego por\u00f3wnania struktur. Przygl\u0105daj\u0105c si\u0119 parametrom oraz u\u017cywanym typom wsz\u0119dzie zobaczymy Object, a to oznacza, \u017ce nie unikniemy boxingu, r\u00f3wnie\u017c przy por\u00f3wnywaniu ka\u017cdego z p\u00f3l. \u0141\u0105cz\u0105c ze sob\u0105 boxing oraz refleksj\u0119 otrzymujemy oczywisty pow\u00f3d, przez kt\u00f3ry mo\u017cemy uzna\u0107 t\u0105 metod\u0119 za niewydajn\u0105 dla struktur.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>GetHashCode w strukturach<\/strong><\/h2>\n\n\n\n<p>Metoda GetHashCode jest nieco bardziej skomplikowana, ale, podobnie jak Equals, posiada do wyboru dwa g\u0142\u00f3wne algorytmy \u2013 szybki oraz wolny \u2013 kt\u00f3rych wyb\u00f3r oparty jest na tych samych warunkach, co poprzednio. Szybki algorytm oblicza kod hash, opierj\u0105c si\u0119 na bajtach, natomiast wolniejszy jest oparty na strategii wyliczanej dla konkretnego typu (<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>Pomijaj\u0105c niepotrzebne nam teraz szczeg\u00f3\u0142y, wybierane jest tylko pierwsze pole z obiektu i wy\u0142\u0105cznie to pole u\u017cywane jest do obliczenia kodu hash. Oznacza to, \u017ce dla ka\u017cdej struktury, kt\u00f3ra posiada co najmniej jedno pole typu referencyjnego, kod hash b\u0119dzie taki sam, je\u015bli pierwsze pole b\u0119dzie mie\u0107 tak\u0105 sam\u0105 warto\u015b\u0107.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Pierwszy problem \u2013 bez nadpisanej metody GetHashCode struktury u\u017cywaj\u0105 tylko pierwszego pola do obliczenia kodu hash<\/strong><\/h2>\n\n\n\n<p>Informacje uzyskane dzi\u0119ki analizie kodu \u017ar\u00f3d\u0142owego klasy ValueType stanowi\u0105 podstaw\u0119 do naprawy implementacji struktury SupplierKey.<\/p>\n\n\n\n<p>Jak si\u0119 dowiedzieli\u015bmy, SupplierKey w obecnej formie u\u017cywa wy\u0142\u0105cznie pierwszego pola (kod kraju) do obliczenia kodu hash. Potwierdza to poni\u017cszy kod, gdzie zmiana identyfikatora subskrypcji nie powoduje wygenerowania nowego kodu hash.<\/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>Oznacza to dla nas, \u017ce w przypadku u\u017cycia tej struktury jako klucza w s\u0142owniku b\u0119dziemy operowa\u0107 na powtarzaj\u0105cych si\u0119 kodach hash. Ma to znaczenie ze wzgl\u0119d\u00f3w wydajno\u015bciowych, poniewa\u017c kod hash klucza jest u\u017cywany do okre\u015blenia, jaki indeks w wewn\u0119trznej tablicy s\u0142ownika zajmie wstawiana przez nas warto\u015b\u0107.<\/p>\n\n\n\n<p>W przypadku wielu obiekt\u00f3w o takim samym kodzie hash, podczas wyci\u0105gania warto\u015bci ze s\u0142ownika nie b\u0119dziemy mogli wykorzysta\u0107 wy\u0142\u0105cznie indeksu, ale r\u00f3wnie\u017c b\u0119dziemy musieli przej\u015b\u0107 przez pozosta\u0142e obiekty o takim samym kodzie hash i sprawdzi\u0107, o kt\u00f3ry z nich tak naprawd\u0119 nam chodzi. Tym samym, nasza zak\u0142adana z\u0142o\u017cono\u015b\u0107 obliczeniowa O(1) zamienia si\u0119 w O(n).<\/p>\n\n\n\n<p>Omawiany kod znajduje si\u0119 wewn\u0105trz metody Dictionary.FindValue i dost\u0119pny jest pod linkiem (<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>Naprawa pierwszego problemu<\/strong><\/h3>\n\n\n\n<p>Uzyskali\u015bmy teraz wystarczaj\u0105cy pow\u00f3d do prze\u0142adowania metody GetHashCode. Musimy u\u017cy\u0107 pozosta\u0142ych pola, tak aby hash obliczany by\u0142 dla ca\u0142ego klucza, a nie tylko dla jego cz\u0119\u015bci. Wykorzystamy do tego metod\u0119 HashCode.Combine().<\/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>Po nadpisaniu metody GetHashCode uzyskujemy inny kod hash przy zmianie warto\u015bci dowolnego z p\u00f3l w naszej strukturze.<\/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>Pozostaje nam jeszcze sprawdzenie, czy uda nam si\u0119 uzyska\u0107 ten sam kod hash dla takich samych warto\u015bci, co potwierdza poni\u017cszy kod.<\/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>Pierwsze testy wydajno\u015bci<\/strong><\/h3>\n\n\n\n<p>W teorii naprawili\u015bmy problem, ale przeprowad\u017amy teraz pierwszy test wydajno\u015bci, sprawdzaj\u0105c, ile czasu zajmie pobranie warto\u015bci ze s\u0142ownika. B\u0119dziemy u\u017cywa\u0107 trzech zestaw\u00f3w danych stanowi\u0105cych iloczyn kartezja\u0144ski dla X kraj\u00f3w oraz Y subskrypcji:<\/p>\n\n\n\n<p>Zestaw 1. X = 50, Y = 50, X\u00d7Y = 2 500,<\/p>\n\n\n\n<p>Zestaw 2. X = 100, Y = 100, X\u00d7Y = 10 000,<\/p>\n\n\n\n<p>Zestaw 3. X = 200, Y = 200, X\u00d7Y = 40 000.<\/p>\n\n\n\n<p>Do przeprowadzenia test\u00f3w wykorzystamy bibliotek\u0119 BenchmarkDotNet oraz .NET 8.0.1. Poni\u017cszy kod przedstawia klas\u0119 DictionaryGetTests, w kt\u00f3rej mo\u017cemy zauwa\u017cy\u0107 nast\u0119puj\u0105ce segmenty:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Pole Size odpowiada za wielko\u015b\u0107 kolekcji. Jego warto\u015b\u0107 b\u0119dzie si\u0119 zmienia\u0107 w zale\u017cno\u015bci od tego, kt\u00f3ry zestaw danych b\u0119dzie u\u017cyty do test\u00f3w.<\/li>\n\n\n\n<li>Metoda IterationSetup wywo\u0142ywana jest przed ka\u017cdym testem i ma za zadanie zainicjalizowa\u0107 s\u0142owniki oraz wstawi\u0107 do nich warto\u015bci umo\u017cliwiaj\u0105ce przeprowadzenie test\u00f3w. Kod znajduj\u0105cy si\u0119 w tej metodzie nie zostanie uwzgl\u0119dniony podczas pomiar\u00f3w wydajno\u015bci.<\/li>\n\n\n\n<li>Metody DictionaryGet_WithoutHashCode oraz DictionaryGet_WithHashCode stanowi\u0105 testowany blok kodu. To w nich nast\u0119puje iteracja po wszystkich krajach i subskrypcjach oraz pobranie warto\u015bci z odpowiedniego s\u0142ownika. Dla DictionaryGet_WithoutHashCode klucz nie posiada nadpisanej metody GetHashCode, a dla DictionaryGet_WithHashCode u\u017cywamy klucza z nadpisan\u0105 metod\u0105 GetHashCode.<\/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    \/\/ Wielko\u015b\u0107 kolekcji\n    &#x5B;Params(50, 100, 200)]\n    public int Size;\n \n    \/\/ Testowane s\u0142owniki\n    private Dictionary&lt;SupplierKey, string&gt; _suppliersMapping_WithoutHashCode;\n    private Dictionary&lt;SupplierKey_WithHashCode, string&gt; _suppliersMapping_WithHashCode;\n \n    \/\/ Dost\u0119pne klucze\n    private SupplierKey&#x5B;] _supplierKeys_WithoutHashCode;\n    private SupplierKey_WithHashCode&#x5B;] _supplierKeys_WithHashCode;\n \n    \/\/ Warto\u015b\u0107 dla ka\u017cdego z kluczy w s\u0142owniku jest niezmienna\n    private const string Value = &quot;supplier&quot;;\n \n    \/\/ Metoda przygotowuj\u0105ca dane testowe \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                \/\/ Do dw\u00f3ch s\u0142ownik\u00f3w umieszczamy klucz o takich samych warto\u015bciach, ale innym typie\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 przeprowadzany dla s\u0142ownika opartego o klucze w postaci niezmienionej struktury\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     \/\/ Test przeprowadzany dla s\u0142ownika opartego o klucze w postaci struktury z nadpisan\u0105 metod\u0105 GetHashCode\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>Wyniki pierwszych test\u00f3w wydajno\u015bci<\/strong><\/h3>\n\n\n\n<p>Po wywo\u0142aniu powy\u017cszego kodu uzyskali\u015bmy wyniki przedstawione na Ryc. 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\">Ryc. 1 Wyniki pierwszych test\u00f3w wydajno\u015bci<\/figcaption><\/figure>\n\n\n\n<p>Od razu mo\u017cemy wyci\u0105gn\u0105\u0107 nast\u0119puj\u0105ce wnioski:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Poprzez nadpisanie metody GetHashCode uda\u0142o nam si\u0119 zredukowa\u0107 czas potrzebny na wykonanie testowanego bloku kodu.<\/li>\n\n\n\n<li>Czas wykonania DictionaryGet_WithHashCode ro\u015bnie w spos\u00f3b zbli\u017cony do liniowego. Dla 40 000 operacji stanowi on 4-krotno\u015b\u0107 czasu potrzebnego na wykonanie 10 000 operacji, gdzie dla DictionaryGet_WithoutHashCode jest to ponad 7 razy wi\u0119cej.<\/li>\n\n\n\n<li>Zar\u00f3wno dla DictionaryGet_WithHashCode jak i DictionaryGet_WithoutHashCode, widzimy du\u017c\u0105 liczb\u0119 bajt\u00f3w zaalokowanych na stercie, kt\u00f3ra ro\u015bnie wraz z powi\u0119kszaniem si\u0119 liczby wykonywanych operacji.<\/li>\n<\/ul>\n\n\n\n<p>Po szybkim przeanalizowaniu wynik\u00f3w widzimy, \u017ce uda\u0142o nam si\u0119 uzyska\u0107 zak\u0142adany rezultat, ale nadal jest miejsce do poprawy. Najbardziej zauwa\u017calna jest ilo\u015b\u0107 alokowanej pami\u0119ci. Stanowi to kolejny problem, jaki b\u0119dziemy rozwi\u0105zywa\u0107.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Drugi problem \u2013 niepotrzebne alokacje pami\u0119ci<\/strong><\/h2>\n\n\n\n<p>\u017beby zrozumie\u0107 problem zwi\u0105zany z alokacjami, musimy znowu przyjrze\u0107 si\u0119 kodowi \u017ar\u00f3d\u0142owemu s\u0142ownik\u00f3w, a konkretniej sposobowi, w jaki s\u0105 one ze sob\u0105 por\u00f3wnywane.<\/p>\n\n\n\n<p>Za znalezienie klucza w s\u0142owniku odpowiedzialna jest metoda Dictionary.FindValue, kt\u00f3ra przeszukuje wewn\u0119trzn\u0105 tablic\u0119 s\u0142ownika i stara si\u0119 zwr\u00f3ci\u0107 warto\u015b\u0107 odpowiadaj\u0105c\u0105 wprowadzonemu kluczowi. Poni\u017cszy kod przedstawia warunek pochodz\u0105cy z 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\/\/ Kod wywo\u0142ywany dla typ\u00f3w warto\u015bciowych bez zdefiniowanego comparera\nif (typeof(TKey).IsValueType &amp;&amp; comparer == null)\n{\n    \/\/ kod pomini\u0119ty na potrzeby artyku\u0142u\n\n         \/\/ Do por\u00f3wnania u\u017cyty zostaje EqualityComparer&lt;TKey&gt;.Default\n    if (entry.hashCode == hashCode &amp;&amp; EqualityComparer&lt;TKey&gt;.Default.Equals(entry.key, key))\n    {\n        goto ReturnFound;\n    }\n\n    \/\/ kod pomini\u0119ty na potrzeby artyku\u0142u\n}\n<\/pre><\/div>\n\n\n<p>Jak widzimy, dla typ\u00f3w warto\u015bciowych u\u017cywany jest domy\u015blny EqualityComparer, ale \u017ceby zrozumie\u0107 co to oznacza, musimy przej\u015b\u0107 dalej do metody CreateDefaultEqualityComparer, kt\u00f3ra wywo\u0142ywana jest z 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    \/\/ kod pomini\u0119ty na potrzeby artyku\u0142u\n\n    \/\/ Warunek sprawdzaj\u0105cy czy klucz w s\u0142owniku implementuje IEquatable&lt;&gt;\n    else if (type.IsAssignableTo(typeof(IEquatable&lt;&gt;).MakeGenericType(type)))\n    {\n        \/\/ Zwracany jest wydajny GenericEqualityComparer\n        result = CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer&lt;string&gt;), runtimeType);\n    }\n\n    \/\/ kod pomini\u0119ty na potrzeby artyku\u0142u\n\n    \/\/ Zwracany jest niewydajny ObjectEqualityComparer\n    return result ?? CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(ObjectEqualityComparer&lt;object&gt;), runtimeType);\n}\n<\/pre><\/div>\n\n\n<p>Jak mo\u017cemy zauwa\u017cy\u0107, je\u015bli klucz w s\u0142owniku jest typem implementuj\u0105cym interfejs IEquatable&lt;&gt;, u\u017cywany jest GenericEqualityComparer oparty na interfejsie IEquatable&lt;&gt;. W przeciwnym wypadku s\u0142ownik u\u017cywa\u0107 b\u0119dzie wolniejszego ObjectEqualityComparer, kt\u00f3ry podczas por\u00f3wnywania musi rzutowa\u0107 nasz typ warto\u015bciowy na obiekt, powoduj\u0105c tym samym niepotrzebne alokacje pami\u0119ci.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Naprawa drugiego problemu<\/strong><\/h3>\n\n\n\n<p>Teraz, kiedy wiemy, co powoduje dodatkowe alokacje, mo\u017cemy przyst\u0105pi\u0107 do zmian w kodzie. Musimy zaimplementowa\u0107 interfejs IEquatable&lt;&gt; wraz z metod\u0105 Equals. Poni\u017cszy kod przedstawia struktur\u0119 SupplierKey_WithHashCode_AndEquals. Zauwa\u017cmy, \u017ce nadpisali\u015bmy te\u017c metod\u0119 Equals pochodz\u0105c\u0105 z bazowego obiektu, aby odpowiada\u0142a tej pochodz\u0105cej z 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    \/\/ Nadpisana metoda Equals operuj\u0105ca na obiekcie\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    \/\/ Zaimplementowana metoda Equals pochodz\u0105ca z 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>Testy wydajno\u015bci po zaimplementowaniu IEquatable&lt;&gt;<\/strong><\/h3>\n\n\n\n<p>Po wprowadzeniu poprawek mo\u017cemy przej\u015b\u0107 do kolejnego testu wydajno\u015bci, kt\u00f3rego wyniki zosta\u0142y przedstawione na Ryc. 2. Najnowsze zmiany zauwa\u017cymy w testach metody DictionaryGet_WithHashCode_AndEquals, kt\u00f3ra bazuje na kluczach w postaci 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\">Ryc. 2 Wyniki test\u00f3w wydajno\u015bci po zaimplementowaniu IEquatable&lt;&gt;<\/figcaption><\/figure>\n\n\n\n<p>Przeanalizujmy teraz uzyskane czasy oraz alokacje:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Zaimplementowanie interfejsu IEquatable pozwoli\u0142o nam przyspieszy\u0107 dzia\u0142anie pobierania warto\u015bci ze s\u0142ownika. Dzia\u0142a to teraz oko\u0142o 6 razy szybciej przy zachowaniu liniowego wzrostu czasu w stosunku do zwi\u0119kszania si\u0119 ilo\u015bci element\u00f3w w s\u0142owniku.<\/li>\n\n\n\n<li>Drastycznie spad\u0142a liczba zaalokowanych bajt\u00f3w w pami\u0119ci. Nie zmienia si\u0119 ona wraz ze wzrostem wielko\u015bci kolekcji. Widoczne jest zaledwie 400 bajt\u00f3w, kt\u00f3re alokowane s\u0105 przez sam\u0105 bibliotek\u0119 BenchmarkDotNet (<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\" >wi\u0119cej informacji pod linkiem<\/a>). Uda\u0142o nam si\u0119 tym samym wyeliminowa\u0107 boxing typ\u00f3w warto\u015bciowych podczas pobierania warto\u015bci ze s\u0142ownika.<\/li>\n<\/ul>\n\n\n\n<p>Dzi\u0119ki drobnym zmianom <strong>uda\u0142o nam si\u0119 uzyska\u0107 kod, kt\u00f3ry nie tylko jest wydajny z punktu widzenia czasu potrzebnego na wykonanie oblicze\u0144, ale r\u00f3wnie\u017c nie powoduje dodatkowych alokacji pami\u0119ci<\/strong>. Teraz mo\u017cemy uzna\u0107, \u017ce nasza struktura zosta\u0142a prawid\u0142owo zaimplementowana.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Por\u00f3wnanie omawianej struktury do odpowiednika jako record struct<\/strong><\/h2>\n\n\n\n<p>Przed wprowadzeniem C# 10 pozostaliby\u015bmy ze struktur\u0105, kt\u00f3r\u0105 opisali\u015bmy pod koniec poprzedniego podpunktu, jednak co\u015b si\u0119 zmieni\u0142o. Wraz z C# 10 i .NET 6 zosta\u0142y wprowadzone typy record struct. Posiadaj\u0105 one ju\u017c wbudowane usprawnienia, kt\u00f3re kolejno nanosili\u015bmy na SupplierKey, wi\u0119c mo\u017cemy sprawdzi\u0107, jak b\u0119d\u0105 si\u0119 zachowywa\u0107 dla naszego przypadku testowego.<\/p>\n\n\n\n<p>Zacznijmy od zdefiniowania nowej struktury SupplierKey_Record, kt\u00f3ra posiada te same pola, co nasza dotychczasowa struktura i jest oznaczona s\u0142owem kluczowym record. Dodatkowo wykorzystamy mo\u017cliwo\u015bci zdefiniowania p\u00f3l bezpo\u015brednio w deklaracji typu, co zosta\u0142o przedstawione na poni\u017cszym kodzie.<\/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>Jak wida\u0107, r\u00f3\u017cnica w poziomie skomplikowania kodu jest ogromna. Wystarczy jedna linia, \u017ceby zapewni\u0107 nam wszystko, co musieli\u015bmy napisa\u0107 r\u0119cznie.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Testy wydajno\u015bci dla record struct<\/strong><\/h3>\n\n\n\n<p>Teraz mo\u017cemy przej\u015b\u0107 do test\u00f3w wydajno\u015bci. Metod\u0119 wykorzystuj\u0105c\u0105 klucze w postaci typu SupplierKey_Record nazwiemy DictionaryGet_Record. Wyniki test\u00f3w wydajno\u015bci dla record struct zosta\u0142y przedstawione na Ryc. 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\">Ryc. 3 Testy wydajno\u015bci dla record struct<\/figcaption><\/figure>\n\n\n\n<p>Widzimy, \u017ce record struct jest szybszy od naszej struktury i uda\u0142o nam si\u0119 uzyska\u0107 jeszcze kr\u00f3tszy czas potrzebny na pobranie warto\u015bci ze s\u0142ownika. Zauwa\u017cmy r\u00f3wnie\u017c, \u017ce DictionaryGet_Record podobnie jak DictionaryGet_WithHashCode_AndEquals nie alokuje pami\u0119ci oraz \u017ce wzrost liczby element\u00f3w powoduje liniowy wzrost czasu wykonania.<\/p>\n\n\n\n<p>Mogliby\u015bmy dalej wprowadza\u0107 zmiany do naszych metod GetHashCode oraz Equals, aby uzyska\u0107 zbli\u017cone wyniki, jednak na tym poprzestaniemy.<\/p>\n\n\n\n<p>Uda\u0142o si\u0119 nam doprowadzi\u0107 do sytuacji, gdzie <strong>nasza struktura jest wydajna, nie powoduje niepotrzebnych alokacji oraz czas potrzebny na pobranie pojedynczej warto\u015bci nie zmienia si\u0119 w zale\u017cno\u015bci od liczby element\u00f3w w s\u0142owniku<\/strong>. Przeprowadzaj\u0105c dok\u0142adniejsze pomiary z wykorzystaniem profiler\u00f3w, czy pr\u00f3buj\u0105c dobra\u0107 lepiej dzia\u0142aj\u0105cy spos\u00f3b obliczania kodu hash, mogliby\u015bmy zbli\u017cy\u0107 si\u0119 do recordu, ale mija si\u0119 to z naszym celem.<\/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.jpg\" alt=\"oferta pracy\" class=\"wp-image-30105\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/praca-k.jpg 737w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/10\/praca-k-300x69.jpg 300w\" sizes=\"(max-width: 737px) 100vw, 737px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Podsumowanie<\/strong><\/h2>\n\n\n\n<p>Analizuj\u0105c stosunkowo prosty przypadek, jakim jest zwyczajna struktura z dwoma polami, uda\u0142o nam si\u0119 przerobi\u0107 spory kawa\u0142ek kodu \u017ar\u00f3d\u0142owego .NET. Zdo\u0142ali\u015bmy dowiedzie\u0107 si\u0119 czego\u015b wi\u0119cej na temat wewn\u0119trznej implementacji struktur oraz powod\u00f3w, dla kt\u00f3rych powinni\u015bmy przestrzega\u0107 powszechnie obowi\u0105zuj\u0105cych zasad projektowania kodu C#. Poprzez implementacj\u0119 jednego interfejsu oraz nadpisanie odpowiednich metod uzyskali\u015bmy 300-krotny wzrost wydajno\u015bci.<\/p>\n\n\n\n<p>Uda\u0142o nam si\u0119 te\u017c wykorzysta\u0107 jedn\u0105 z nowinek z ostatnich lat w postaci record struct, co umo\u017cliwi\u0142o nam znaczne uproszczenie kodu naszej struktury i osi\u0105gni\u0119cie jeszcze kr\u00f3tszego czasu potrzebnego na pobranie warto\u015bci ze s\u0142ownika. Zach\u0119ca to nas do \u015bledzenia zmian wprowadzanych do nowych wersji .NET oraz utwierdza w przekonaniu, \u017ce rozw\u00f3j platformy zmierza w dobrym kierunku.<\/p>\n\n\n\n<p>***<\/p>\n\n\n\n<p>Je\u015bli interesuje Ci\u0119 C#, zajrzyj r\u00f3wnie\u017c <a href=\"https:\/\/sii.pl\/blog\/wyszukiwarka\/C%23\/\" target=\"_blank\" aria-label=\"do innych artyku\u0142\u00f3w naszych specjalist\u00f3w (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">do innych artyku\u0142\u00f3w naszych specjalist\u00f3w<\/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;29223&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;16&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;4.4&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;4.4\\\/5 ( votes: 16)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;U\u017cycie struktur jako kluczy z\u0142o\u017conych w s\u0142ownikach w C#&quot;,&quot;width&quot;:&quot;122.1&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: 122.1px;\">\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            4.4\/5 ( votes: 16)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Podczas pracy ze zbiorami danych cz\u0119sto zdarza si\u0119 nam u\u017cy\u0107 kolekcji umo\u017cliwiaj\u0105cych dost\u0119p do danych po kluczu. U\u017cycie s\u0142ownika pozwala &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/uzycie-struktur-jako-kluczy-zlozonych-w-slownikach-w-c\/\">Continued<\/a><\/p>\n","protected":false},"author":674,"featured_media":29230,"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":[1314],"tags":[2427,1512,129],"class_list":["post-29223","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development-na-twardo","tag-digital","tag-poradnik","tag-c"],"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":["Development na twardo"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/29223"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/users\/674"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/comments?post=29223"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/29223\/revisions"}],"predecessor-version":[{"id":30107,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/29223\/revisions\/30107"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media\/29230"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media?parent=29223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/categories?post=29223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/tags?post=29223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}