{"id":983,"date":"2009-11-16T23:54:00","date_gmt":"2009-11-16T23:54:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/vbteam\/2009\/11\/16\/spot-the-bug-the-key-to-using-anonymous-types-jonathan-aneja\/"},"modified":"2024-07-05T12:42:17","modified_gmt":"2024-07-05T19:42:17","slug":"spot-the-bug-the-key-to-using-anonymous-types-jonathan-aneja","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/vbteam\/spot-the-bug-the-key-to-using-anonymous-types-jonathan-aneja\/","title":{"rendered":"Spot the Bug! &#8211; The Key to using Anonymous Types (Jonathan Aneja)"},"content":{"rendered":"<p class=\"MsoNormal\"><font size=\"3\"><i><font face=\"Calibri\">This one&rsquo;s going to be long, but for those of you who&rsquo;ve felt the first 3 <a href=\"http:\/\/blogs.msdn.com\/vbteam\/archive\/tags\/Spot+the+Bug_2100_\/default.aspx\">in this series<\/a> were too easy I promise this one&rsquo;s tougher <\/font><\/i><i><span><span>J<\/span><\/span><font face=\"Calibri\">.<\/p>\n<p><\/font><\/i><\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Let&rsquo;s say you want to list all the customers from a table in a ComboBox, and update the UI based on which one is selected.<span>&nbsp; <\/span>To do this we&rsquo;ll need to bring back two fields from the database &ndash; the customer&rsquo;s name and the customer&rsquo;s ID.<span>&nbsp; <\/span>When a customer is selected we want the ComboBox&rsquo;s SelectedValue property to equal the customer&rsquo;s ID.<\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Here&rsquo;s some quick code that gets us up and running (using Northwind and LINQ to SQL):<\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> db <span>As<\/span> <span>New<\/span> NorthwindDataContext<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> query = <span>From<\/span> row <span>In<\/span> db.Customers _<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Select<\/span> row.CompanyName, row.CustomerID<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>ComboBox1.DataSource = query.ToList()<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>ComboBox1.DisplayMember = <span>&#8220;CompanyName&#8221;<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>ComboBox1.ValueMember = <span>&#8220;CustomerID&#8221;<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">But now let&rsquo;s say your boss looks at the app and says &ldquo;I want you to add an &lsquo;All Customers&rsquo; option as the first item in the list.&rdquo;<span>&nbsp; <\/span>How would you do that?<span>&nbsp; <\/span>You&rsquo;d need to insert an anonymous type into an existing sequence of anonymous types, which is tricky given that you can never actually use the type&rsquo;s name.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Thankfully there&rsquo;s a trick that uses generic parameter inference that allows us to do this (it relies on the fact that the compiler will share (or &ldquo;unify&rdquo;) the definition of multiple anonymous types when they have the same number of members, in the same order, of the same type, with the same names, and the same mutability):<\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span>Dim<\/span><span> db <span>As<\/span> <span>New<\/span> NorthwindDataContext<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> query = <span>From<\/span> row <span>In<\/span> db.Customers _<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Select<\/span> row.CompanyName, row.CustomerID<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> allOption = <span>New<\/span> <span>With<\/span> {.CompanyName = <span>&#8220;All Customers&#8221;<\/span>, _<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>.CustomerID = <span>&#8220;-1&#8221;<\/span>}<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span>ComboBox1.DataSource = AddOptionForAll(query, allOption).ToList()<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span>ComboBox1.DisplayMember = <span>&#8220;CompanyName&#8221;<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span>ComboBox1.ValueMember = <span>&#8220;CustomerID&#8221;<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span>&#8230;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Function<\/span> AddOptionForAll(<span>Of<\/span> T)(<span>ByVal<\/span> sequence <span>As<\/span> IEnumerable(<span>Of<\/span> T), _<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ByVal<\/span> allOption <span>As<\/span> T) <span>As<\/span> IEnumerable(<span>Of<\/span> T)<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216;wrap individual element in an array and then union the two sequences<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;<\/span><span>Return<\/span> (<span>New<\/span> T() {allOption}).Union(sequence)<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Function<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">As long as we have an anonymous type that&rsquo;s compatible with the anonymous type that the query generated, the compiler will determine that <i>sequence <\/i>and <i>allOption<\/i> are actually the same type and this should work fine.<\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">Ok so let&rsquo;s get to the bug &ndash; well in this case <i>spotting <\/i>it is pretty simple (it doesn&rsquo;t compile <\/font><span><span>J<\/span><\/span><font face=\"Calibri\">), but fixing it is tricky (though I&rsquo;ve already given two pretty big hints). <span>&nbsp;<\/span>Here&rsquo;s the text of the compiler error (it&rsquo;s on the line that calls AddOptionForAll):<\/font><\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><i><font size=\"3\"><font face=\"Calibri\">Data type(s) of the type parameter(s) in method &#8216;Public Function AddOptionForAll(Of T)(sequence As System.Collections.Generic.IEnumerable(Of T), allOption As T) As System.Collections.Generic.IEnumerable(Of T)&#8217; cannot be inferred from these arguments because they do not convert to the same type. Specifying the data type(s) explicitly might correct this error.<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><\/p>\n<p><\/font><\/font><\/i><\/p>\n<p class=\"MsoNormal\"><i><\/p>\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<p><\/i><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">What&rsquo;s wrong and how do we fix it?<\/font><\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\" face=\"\nCalibri\">.<\/font><\/p>\n<p class=\"MsoNoSpacing\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\" face=\"Calibri\">.<\/font><\/p>\n<p class=\"MsoNoSpacing\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\" face=\"Calibri\">.<\/font><\/p>\n<p class=\"MsoNoSpacing\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\" face=\"Calibri\">.<\/font><\/p>\n<p class=\"MsoNoSpacing\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNoSpacing\"><font size=\"3\" face=\"Calibri\">.<\/font><\/p>\n<p class=\"MsoNoSpacing\"><b><\/p>\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<p><\/b><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\"><b>Answer:<span>&nbsp; <\/span><\/b>We actually told the compiler to generate two <i>different<\/i> anonymous type definitions, and thus it can&rsquo;t unify them because the types really are different.<span>&nbsp; <\/span><span>&nbsp;<\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">From earlier: &ldquo;&hellip;the compiler will share (or &ldquo;unify&rdquo;) the definition of multiple anonymous types when they have the same number of members, in the same order, with the same names, and <b>the same mutability<\/b>&rdquo;<\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">The anonymous type that the query generates will have ReadOnly properties, whereas the anonymous type that we generated (allOption) will have Read\/Write properties.<span>&nbsp; <\/span>The fix is to allOption immutable so that its structure will match the result of the query:<\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> allOption = <span>New<\/span> <span>With<\/span> {<span>Key<\/span> .CompanyName = <span>&#8220;All Customers&#8221;<\/span>, _<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Key<\/span> .CustomerID = <span>&#8220;-1&#8221;<\/span>}<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">The &ldquo;Key&rdquo; modifier tells the compiler to make those properties ReadOnly and to override Equals and GetHashCode such that they only consider &ldquo;Key&rdquo; properties when deciding if two instances of the same anonymous type are equal.<span>&nbsp; <\/span>(the other hint was in the title <\/font><span><span>J<\/span><\/span><font face=\"Calibri\">).<span>&nbsp; <\/span>For the query above, the compiler automatically inserts the &ldquo;Key&rdquo; modifier &#8211; i.e. what we wrote is exactly equivalent to this:<\/font><\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> query = <span>From<\/span> row <span>In<\/span> db.Customers _<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Select<\/span> <span>New<\/span> <span>With<\/span> {<span>Key<\/span> row.CompanyName, <span>Key<\/span> row.CustomerID}<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\"><font size=\"3\">So with a simple fix to the allOption line we&rsquo;re now using the same anonymous type definition and everything works fine.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\">\n<p><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">VB&rsquo;s anonymous type syntax is very flexible and provides three different options: immutable, fully mutable, or partially mutable (i.e. some fields are ReadOnly while others are not).<span>&nbsp; <\/span>Even for a simple scenario like adding an option to a list, the better you understand how things work under the covers the easier it&rsquo;ll be to debug problems later <\/font><span><span>J<\/span><\/span><font face=\"Calibri\">.<\/font><\/font><\/p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This one&rsquo;s going to be long, but for those of you who&rsquo;ve felt the first 3 in this series were too easy I promise this one&rsquo;s tougher J. &nbsp; Let&rsquo;s say you want to list all the customers from a table in a ComboBox, and update the UI based on which one is selected.&nbsp; To [&hellip;]<\/p>\n","protected":false},"author":260,"featured_media":8818,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[195],"tags":[67,77,83,94,117,165,166,167],"class_list":["post-983","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-visual-basic","tag-extension_methods","tag-iqueryable","tag-jonathan-aneja","tag-linqvb9","tag-orcas","tag-vb2005","tag-vb2008","tag-vb2010"],"acf":[],"blog_post_summary":"<p>This one&rsquo;s going to be long, but for those of you who&rsquo;ve felt the first 3 in this series were too easy I promise this one&rsquo;s tougher J. &nbsp; Let&rsquo;s say you want to list all the customers from a table in a ComboBox, and update the UI based on which one is selected.&nbsp; To [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/983","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/users\/260"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/comments?post=983"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/983\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media\/8818"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media?parent=983"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/categories?post=983"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/tags?post=983"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}