{"id":253,"date":"2014-08-05T16:22:00","date_gmt":"2014-08-05T16:22:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/dotnet\/2014\/08\/05\/multidictionary-becomes-multivaluedictionary\/"},"modified":"2021-09-30T16:48:39","modified_gmt":"2021-09-30T23:48:39","slug":"multidictionary-becomes-multivaluedictionary","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/multidictionary-becomes-multivaluedictionary\/","title":{"rendered":"MultiDictionary becomes MultiValueDictionary"},"content":{"rendered":"<p style=\"padding-left: 30px;\"><em>We just shipped an update to our experimental implementation of a multi value dictionary. In this post, <strong>our software developer intern Ian Hays<\/strong> talks about the changes. &#8212; Immo<\/em><\/p>\n<h2>Goodbye MultiDictionary<\/h2>\n<p>In my <a href=\"http:\/\/blogs.msdn.com\/b\/dotnet\/archive\/2014\/06\/20\/would-you-like-a-multidictionary.aspx\">last post<\/a> I went over <code>MultiDictionary<\/code>, officially available on NuGet as the prerelease package <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Experimental.Collections\">Microsoft.Experimental.Collections<\/a>. We received great feedback, questions and commentary in the comments, and it was clear that this was something that a lot of you felt passionately about (70 comments? Awesome!). We\u2019ve read all of your comments and taken them into consideration for this next iteration of <code>Microsoft.Experimental.Collections<\/code>.<\/p>\n<p>You should also check out our interview on <a href=\"https:\/\/channel9.msdn.com\/Blogs\/Charles\/Ian-Hays-Building-a-MultiDictionary-Collection-for-NET\">Channel 9<\/a>:<\/p>\n<p style=\"text-align: center;\"><iframe style=\"height: 320px; width: 540px;\" src=\"https:\/\/channel9.msdn.com\/Blogs\/Charles\/Ian-Hays-Building-a-MultiDictionary-Collection-for-NET\/player?h=405&amp;w=720\" frameborder=\"0\" scrolling=\"no\"><\/iframe><\/p>\n<h2>Hello MultiValueDictionary<\/h2>\n<p>First off, let\u2019s talk about the name. It was a bit ambiguous what the \u201cMulti\u201d in \u201cMultiDictionary\u201d referred to: at first glance, \u201cmulti\u201d could mean there were multiple keys per value, or a dictionary of dictionaries, or that it was a bi-directional dictionary. To make it explicit and leave room for other variants in the future, we\u2019ve renamed the type to <code>MultiValueDictionary<\/code> to clarify that the type allows multiple values for a single key.<\/p>\n<p>Let\u2019s get right to the meat of the post: what\u2019s changed? We\u2019ll go into some of the major design decisions and changes that make up the new <code>MultiValueDictionary<\/code> in the next sections.<\/p>\n<h2>IEnumerable of\u2026?<\/h2>\n<p><code>MultiDictionary<\/code> could be thought of as <code>Dictionary&lt;TKey, TValue&gt;<\/code> where we could have multiple elements with the same <code>TKey<\/code>. <code>MultiValueDictionary<\/code> is more akin to a <code>Dictionary&lt;TKey, IReadOnlyCollection&lt;TValue&gt;&gt;<\/code> with a number of methods to enable easy modification of the internal <code>IReadOnlyCollections<\/code>. This distinction may seem subtle, but it affects how you consume the data structure.<\/p>\n<p>For example, let\u2019s look at the Count and Values properties. <code>MultiDictionary<\/code> would return the number of values and a collection of values, while <code>MultiValueDictionary<\/code> returns the number of keys and a collection of <code>IReadOnlyCollections<\/code> of values.<\/p>\n<pre><code>\/\/ MultiDictionary<\/code><\/pre>\n<p>var multiDictionary = new MultiDictionary&lt;string, int&gt;();\nmultiDictionary.Add(&#8220;key&#8221;, 1);\nmultiDictionary.Add(&#8220;key&#8221;, 2);\n\/\/multiDictionary.Count == 2\n\/\/multiDictionary.Values contains elements [1,2]<\/p>\n<pre><code><\/code><\/pre>\n<p>\/\/ MultiValueDictionary<\/p>\n<pre><code><\/code><\/pre>\n<p>var multiValueDictionary = new MultiValueDictionary&lt;string, int&gt;();\nmultiValueDictionary.Add(&#8220;key&#8221;, 1);\nmultiValueDictionary.Add(&#8220;key&#8221;, 2);\n\/\/multiValueDictionary.Count == 1\n\/\/multiValueDictionary.Values contains elements [[1,2]]<\/p>\n<pre><code><\/code><\/pre>\n<p>This behavioral change also affects the enumerator in the same way that it affects the <code>Values<\/code> property. Previously the dictionary was flattened when enumerating, as it implemented <code>IEnumerable&lt;KeyValuePair&lt;TKey, TValue&gt;&gt;<\/code>. <code>MultiValueDictionary<\/code> now implements <code>IEnumerable&lt;KeyValuePair&lt;TKey, IReadOnlyCollection&lt;TValue&gt;&gt;<\/code>.<\/p>\n<pre><code>var multiValueDictionary = new MultiValueDictionary&lt;string, int&gt;();\r\nmultiValueDictionary.Add(\"key\", 1);\r\nmultiValueDictionary.Add(\"key\", 2);\r\nmultiValueDictionary.Add(\"anotherKey\", 3);<\/code><\/pre>\n<p>foreach (KeyValuePair&lt;string, IReadOnlyCollection&lt;int&gt;&gt; key in multiValueDictionary)\n{\nforeach (int value in key.Value)\n{\nConsole.WriteLine(&#8220;{0}, {1}&#8221;, key.Key, value);\n}\n}\n\/\/ key, 1\n\/\/ key, 2\n\/\/ anotherKey, 3<\/p>\n<pre><code><\/code><\/pre>\n<p>As Sinix pointed out in the previous blog post comments, this is very similar to another type in the .NET Framework, <code>ILookup&lt;TKey, TValue&gt;<\/code>. <code>MultiValueDictionary<\/code> shouldn\u2019t implement both the dictionary and lookup interfaces, because that would cause it through interface inheritance to implement two different versions of <code>IEnumerable<\/code>: <code>IEnumerable&lt;KeyValuePair&lt;TKey, IReadOnlyCollection&lt;TValue&gt;&gt;<\/code> and <code>IEnumerable&lt;IGrouping&lt;TKey, TValue&gt;<\/code>. It wouldn\u2019t be clear which version you would get when using <code>foreach<\/code>. But since <code>MultiValueDictionary<\/code> logically implements the concept, we\u2019ve added a method <code>AsLookup()<\/code> to <code>MultiValueDictionary<\/code> which returns an implementation of the <code>ILookup<\/code> interface.<\/p>\n<pre><code>var multiValueDictionary = new MultiValueDictionary&lt;string, int&gt;();\r\nmultiValueDictionary.Add(\"key\", 1);\r\nmultiValueDictionary.Add(\"key\", 2);\r\nmultiValueDictionary.Add(\"anotherKey\", 3);<\/code><\/pre>\n<p>var lookup = multiValueDictionary.AsLookup();\nforeach (IGrouping&lt;string, int&gt; group in lookup)\n{\nforeach (int value in group)\n{\nConsole.WriteLine(&#8220;{0}, {1}&#8221;, group.Key, value);\n}\n}\n\/\/ key, 1\n\/\/ key, 2\n\/\/ anotherKey, 3<\/p>\n<pre><code><\/code><\/pre>\n<h2>Indexing and TryGetValue<\/h2>\n<p>In the first iteration of the <code>MultiDictionary<\/code> we followed the precedent from Linq\u2019s <code>AsLookup()<\/code> with regards to the way the indexation into the <code>MultiDictionary<\/code> worked. In a regular <code>Dictionary<\/code>, if you attempt to index into a key that isn\u2019t present you\u2019ll get a <code>KeyNotFoundException<\/code>, but like <code>AsLookup()<\/code>, the <code>MultiDictionary<\/code> returned an empty list instead. This was mostly to match the functionality of the <code>Lookup<\/code> class that is conceptually similar to the <code>MultiDictionary<\/code>, but also because this behavior was more practically applicable to the kinds of things you\u2019d be using the <code>MultiDictionary<\/code>.<\/p>\n<p>With the behavior changes brought on by the <code>MultiValueDictionary<\/code> and the addition of the <code>AsLookup()<\/code> method, this old functionality doesn\u2019t quite fit anymore. We heard feedback that this inconsistency between <code>MultiDictionary<\/code> and <code>Dictionary<\/code> was confusing, so the <code>MultiValueDictionary<\/code> will now throw a <code>KeyNotFoundException<\/code> when indexing on a key that isn\u2019t present. We\u2019ve also added a <code>TryGetValue<\/code> method to accommodate the new behavior.<\/p>\n<pre><code>var multiValueDictionary = new MultiValueDictionary&lt;string, int&gt;();\r\nmultiValueDictionary.Add(\"key\", 1);\r\n\/\/multiValueDictionary[\"notkey\"] throws a KeyNotFoundException\r\nIReadOnlyCollection&lt;int&gt; collection = multiValueDictionary[\"key\"];\r\nmultiValueDictionary.Add(\"key\", 2);\r\n\/\/collection contains values [1,2]<\/code><\/pre>\n<p>Another related change with the <code>MultiValueDictionary<\/code> on the topic of the indexer is the return value. Previously we returned a mutable <code>ICollection&lt;TValue&gt;<\/code>. Adding and removing values from the returned <code>ICollection&lt;TValue&gt;<\/code> updated the <code>MultiDictionary<\/code>. While there are uses for this functionality, it can be unexpected and create unintentional coupling between parts of an application. To address this we\u2019ve changed the return type to <code>IReadOnlyCollection&lt;TValue&gt;<\/code>. The read-only collection will still update with changes to the <code>MultiValueDictionary<\/code>.<\/p>\n<h2>When a List just doesn\u2019t cut it<\/h2>\n<p>One limitation of the <code>MultiDictionary<\/code> was that internally, it used a <code>Dictionary&lt;TKey, List&lt;TValue&gt;&gt;<\/code> and there was no way to change the inner collection type. With the <code>MultiValueDictionary<\/code> we\u2019ve added the ability to specify your own inner collection.<\/p>\n<p>Showing a simple example of how they work is probably easier than trying to describe them first, so let\u2019s do that.<\/p>\n<pre><code>var multiValueDictionary = MultiValueDictionary&lt;string, int&gt;.Create&lt;HashSet&lt;int&gt;&gt;();\r\nmultiValueDictionary.Add(\"key\", 1);\r\nmultiValueDictionary.Add(\"key\", 1);\r\n\/\/multiDictionary[\"key\"].Count == 1<\/code><\/pre>\n<p>Above, we replace the default <code>List&lt;TValue&gt;<\/code> with a <code>HashSet&lt;TValue&gt;<\/code>. As the examples show, <code>HashSet<\/code> combines duplicate <code>TValues<\/code>.<\/p>\n<p>For every constructor there is a parallel generic static <code>Create<\/code> method that takes the same parameters but allows specification of the interior collection type. It\u2019s important to point out that this doesn\u2019t affect the return value of the indexer\/<code>TryValueValue<\/code> though (they return very limited <code>IReadOnlyCollections<\/code> regardless of the inner collection type).<\/p>\n<p>If you want a little bit more control over how your custom collection is instantiated, there are also the more specific <code>Create<\/code> methods that allow you to pass a delegate to specify the inner collection type:<\/p>\n<pre><code>var multiValueDictionary = MultiValueDictionary&lt;string, int&gt;.Create&lt;HashSet&lt;int&gt;&gt;(myHashSetFactory);\r\nmultiValueDictionary.Add(\"key\", 1);\r\nmultiValueDictionary.Add(\"key\", 1);\r\n\/\/multiValueDictionary[\"key\"].Count == 1 <\/code><\/pre>\n<p>In either case, the specified collection type must implement <code>ICollection&lt;TValue&gt;<\/code> and must not have <code>IsReadOnly<\/code> set to true by default.<\/p>\n<h2>And that\u2019s all!<\/h2>\n<p>You can download the new <code>MultiValueDictionary<\/code> from <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Experimental.Collections\">NuGet<\/a> and try it out for yourself! If you have any questions or if you just want to give feedback, please leave a comment or <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Experimental.Collections\/1.0.1-alpha\/ContactOwners\">contact us<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We just shipped an update to our experimental implementation of a multi value dictionary. In this post, our software developer intern Ian Hays talks about the changes. &#8212; Immo Goodbye MultiDictionary In my last post I went over MultiDictionary, officially available on NuGet as the prerelease package Microsoft.Experimental.Collections. We received great feedback, questions and commentary [&hellip;]<\/p>\n","protected":false},"author":335,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685],"tags":[],"class_list":["post-253","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet"],"acf":[],"blog_post_summary":"<p>We just shipped an update to our experimental implementation of a multi value dictionary. In this post, our software developer intern Ian Hays talks about the changes. &#8212; Immo Goodbye MultiDictionary In my last post I went over MultiDictionary, officially available on NuGet as the prerelease package Microsoft.Experimental.Collections. We received great feedback, questions and commentary [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/253","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/335"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=253"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/253\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=253"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=253"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=253"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}