{"id":358,"date":"2018-04-11T19:29:39","date_gmt":"2018-04-11T09:29:39","guid":{"rendered":"http:\/\/www.nerdhold.com\/coder\/?p=358"},"modified":"2019-06-07T20:40:40","modified_gmt":"2019-06-07T10:40:40","slug":"how-csharp-object-extend-works","status":"publish","type":"post","link":"http:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/","title":{"rendered":"How my C# Object.Extend implementation works"},"content":{"rendered":"<p>I&#8217;ve had a few people ask me to explain how my C# Object.Extend implementation works &#8211; so here I am, going into more detail than I expected people would be interested in.<\/p>\n<p>Making <code>Object.Extend<\/code> work seemed straight-forward at first. You can read my initial code in <a title=\"Object.Extend in C# for exploratory coding\" href=\"http:\/\/www.nerdhold.com\/coder\/2018\/04\/09\/object-extend-linqpad\/\">my original ObjectExtend post<\/a>. The problem started when I tried to chain two calls together: I immediately got a <code>RuntimeBinderException<\/code>. Unfortunately, for a really good reason (the difficulty of discovering the right namespaces to search), the code responsible for runtime method binding doesn&#8217;t look for extension methods. That means that when the first call to <code>.Extend<\/code> returns something marked as <code>dynamic<\/code> (regardless of what actual type it returns), you can&#8217;t use any extension methods on the result. Attempting to call either <code>.Extend<\/code> again or <code>.Dump<\/code> (another common extension method in LINQPad) results in the dreaded <code>RuntimeBinderException<\/code>.<\/p>\n<p>The usual usage of the <code>dynamic<\/code> keyword is to let us return something like the <code>ExpandoObject<\/code> type, like this:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\ndynamic ReturnSomething() {\r\n\treturn new ExpandoObject();\r\n}\r\n<\/pre>\n<p>Fortunately, the <code>dynamic<\/code> keyword in C# essentially just tells the compiler to engage duck-typing mode &#8211; it doesn&#8217;t actually tell the run-time anything. So there&#8217;s nothing stopping us from doing this:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\ndynamic ReturnSomething() {\r\n\treturn new List&lt;string&gt;();\r\n}\r\n<\/pre>\n<p>The compiler (and your IDE) will allow you to try to call anything at all on the return value of <code>ReturnSomething<\/code>, and hand everything off to the runtime binder. This means that you can&#8217;t use extension methods on anything returned by a method marked <code>dynamic<\/code>, even if you actually return a regular non-dynamic type.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\ndynamic ReturnSomething() {\r\n\treturn new object();\r\n}\r\nReturnSomething().Dump();\r\n<\/pre>\n<p>Nope. Instant <code>RuntimeBinderException<\/code>.<\/p>\n<p>There&#8217;s one situation which will give us exactly what we want &#8211; if we mark the <code>.Extend<\/code> function as <code>dynamic<\/code>, but the type we return has our <code>Extend<\/code> method built right in to it. This means our IDE (LINQPad, in my case) will allow us to access any of the dynamic properties we build up on our types over successive calls to <code>Extend<\/code>, and the runtime binder will be able to find the methods because they&#8217;re right on the type we return &#8211; we&#8217;re not relying on extension methods!<\/p>\n<p>I initially thought I could create a subclass of <code>ExpandoObject<\/code> with calls for <code>Extend<\/code> and <code>Dump<\/code>, but it turns out that not only is <code>ExpandoObject<\/code> a sealed class, it&#8217;s also a bit special in other ways &#8211; so that was a no-go.<\/p>\n<p>So now the only problem we have is that we need to create types on the fly which contain functions for <code>Extend<\/code> (and <code>Dump<\/code>, for convenience), and also whatever properties we want to dynamically add &#8211; the union of all of the properties on the original object and the objects we want to extend it with. I looked into a few alternatives, and a good compromise was the <code>Microsoft.CSharp.CSharpCodeProvider<\/code> &#8211; it allows me to dynamically assemble a syntax tree and get the framework to generate in-memory assemblies on-the-fly. The details are a little tedious, but it&#8217;s very possible to use this to create types on the fly &#8211; containing both the method calls we want for <code>Dump<\/code> and <code>Extend<\/code> as well as all of the properties we need. We can then instantiate our dynamically-created type and copy all of the values onto it. Our IDE will let us access our runtime-created properties and methods without compile-time errors, because our function is marked as dynamic &#8211; and the runtime binder can find all of the properties, as well as our <code>.Extend<\/code> and <code>.Dump<\/code> methods, because they&#8217;re actually on the type the runtime binder is looking at.<\/p>\n<p>The minimum viable code to do something useful with <code>CSharpCodeProvider<\/code> looks something like this (note that this requires a couple of helpers you can find in the class linked below):<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nvar compileUnit = new CodeCompileUnit();\r\nvar nameSpace = new CodeNamespace(&quot;MyDynamicNamespace&quot;);\r\nvar classType = new CodeTypeDeclaration(&quot;MyNewType&quot;);\r\nclassType.Attributes = MemberAttributes.Public;\r\ncompileUnit.Namespaces.Add(nameSpace);\r\nnameSpace.Types.Add(classType);\r\nvar returnThree = new CodeMemberMethod {\r\n\tName = &quot;ReturnThree&quot;,\r\n\tAttributes = MemberAttributes.Public,\r\n\tReturnType = new CodeTypeReference(typeof(int))\r\n};\r\nreturnThree.Statements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(3)));\r\nclassType.Members.Add(returnThree);\r\nvar result = _cSharpCodeProvider.Value.CompileAssemblyFromDom(_compilerParameters.Value, compileUnit);\r\nvar compiledType = result.CompiledAssembly.GetTypes().Single();\r\nLINQPad.Extensions.Dump(((dynamic)Activator.CreateInstance(compiledType)).ReturnThree());\r\n<\/pre>\n<p>This is a very round-about way to output the number 3, but it works!<br \/>\n<a href=\"http:\/\/www.nerdhold.com\/coder\/files\/2018\/04\/PrintThree.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-375\" alt=\"PrintThree\" src=\"http:\/\/www.nerdhold.com\/coder\/files\/2018\/04\/PrintThree.png\" width=\"1632\" height=\"265\" srcset=\"http:\/\/www.nerdhold.com\/coder\/files\/2018\/04\/PrintThree.png 1632w, http:\/\/www.nerdhold.com\/coder\/files\/2018\/04\/PrintThree-300x48.png 300w, http:\/\/www.nerdhold.com\/coder\/files\/2018\/04\/PrintThree-1024x166.png 1024w\" sizes=\"(max-width: 1632px) 100vw, 1632px\" \/><\/a><\/p>\n<p>I won&#8217;t reproduce the code here, but you can find all the details of the Object.Extend dynamic type creation in the <code>CreateTypeUncached<\/code> function in <a title=\"ObjectExtend.cs\" href=\"https:\/\/github.com\/Rophuine\/ObjectExtend\/blob\/bedd617e7d23ae050ce9a535182a04ec432ef94a\/ObjectExtend\/ObjectExtend.cs#L53\" target=\"_blank\">ObjectExtend.cs<\/a>.<\/p>\n<p>You might notice the word &#8216;Uncached&#8217; there. When I first tried this approach, it was horrifyingly slow &#8211; I was using Object.Extend in a <code>Select<\/code> statement against a large <code>IEnumerable<\/code>, and generating many identical types. Throwing a quick cache into the mix based on the name and type of all properties of the type we need vastly reduces the number of calls to the compiler service and brings performance up to a tolerable level.<\/p>\n<p>While I have glossed over some details, hopefully this explanation will give readers some background information to aid in reading the code. Please feel free to reach out to me <a title=\"@rophuine on Twitter\" href=\"https:\/\/twitter.com\/rophuine\" target=\"_blank\">on Twitter<\/a> and let me know if parts of my explanation are hard to follow.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve had a few people ask me to explain how my C# Object.Extend implementation works &#8211; so here I am, going into more detail than I expected people would be interested in. Making Object.Extend work seemed straight-forward at first. You can read my initial code in my original ObjectExtend post. The problem started when I &hellip; <a href=\"http:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">How my C# Object.Extend implementation works<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[18,13,12],"tags":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v21.8 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>How my C# Object.Extend implementation works - Nerdhold Coder<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How my C# Object.Extend implementation works - Nerdhold Coder\" \/>\n<meta property=\"og:description\" content=\"I&#8217;ve had a few people ask me to explain how my C# Object.Extend implementation works &#8211; so here I am, going into more detail than I expected people would be interested in. Making Object.Extend work seemed straight-forward at first. You can read my initial code in my original ObjectExtend post. The problem started when I &hellip; Continue reading How my C# Object.Extend implementation works &rarr;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/\" \/>\n<meta property=\"og:site_name\" content=\"Nerdhold Coder\" \/>\n<meta property=\"article:published_time\" content=\"2018-04-11T09:29:39+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-06-07T10:40:40+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/www.nerdhold.com\/coder\/files\/2018\/04\/PrintThree.png\" \/>\n<meta name=\"author\" content=\"Lionell Pack\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Lionell Pack\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"4 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/\",\"url\":\"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/\",\"name\":\"How my C# Object.Extend implementation works - Nerdhold Coder\",\"isPartOf\":{\"@id\":\"http:\/\/www.nerdhold.com\/coder\/#website\"},\"datePublished\":\"2018-04-11T09:29:39+00:00\",\"dateModified\":\"2019-06-07T10:40:40+00:00\",\"author\":{\"@id\":\"http:\/\/www.nerdhold.com\/coder\/#\/schema\/person\/ca2988d5c0cb756a846e4d8c54e86b77\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"http:\/\/www.nerdhold.com\/coder\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How my C# Object.Extend implementation works\"}]},{\"@type\":\"WebSite\",\"@id\":\"http:\/\/www.nerdhold.com\/coder\/#website\",\"url\":\"http:\/\/www.nerdhold.com\/coder\/\",\"name\":\"Nerdhold Coder\",\"description\":\"Tinkerings of a C# Coder\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"http:\/\/www.nerdhold.com\/coder\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"http:\/\/www.nerdhold.com\/coder\/#\/schema\/person\/ca2988d5c0cb756a846e4d8c54e86b77\",\"name\":\"Lionell Pack\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"http:\/\/www.nerdhold.com\/coder\/#\/schema\/person\/image\/\",\"url\":\"http:\/\/0.gravatar.com\/avatar\/9be7b23cd97814ac4a40b9b4d2955b5a?s=96&d=mm&r=pg\",\"contentUrl\":\"http:\/\/0.gravatar.com\/avatar\/9be7b23cd97814ac4a40b9b4d2955b5a?s=96&d=mm&r=pg\",\"caption\":\"Lionell Pack\"},\"sameAs\":[\"http:\/\/blog.rophuine.net\"],\"url\":\"http:\/\/www.nerdhold.com\/coder\/author\/admin\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How my C# Object.Extend implementation works - Nerdhold Coder","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/","og_locale":"en_US","og_type":"article","og_title":"How my C# Object.Extend implementation works - Nerdhold Coder","og_description":"I&#8217;ve had a few people ask me to explain how my C# Object.Extend implementation works &#8211; so here I am, going into more detail than I expected people would be interested in. Making Object.Extend work seemed straight-forward at first. You can read my initial code in my original ObjectExtend post. The problem started when I &hellip; Continue reading How my C# Object.Extend implementation works &rarr;","og_url":"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/","og_site_name":"Nerdhold Coder","article_published_time":"2018-04-11T09:29:39+00:00","article_modified_time":"2019-06-07T10:40:40+00:00","og_image":[{"url":"http:\/\/www.nerdhold.com\/coder\/files\/2018\/04\/PrintThree.png"}],"author":"Lionell Pack","twitter_misc":{"Written by":"Lionell Pack","Est. reading time":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/","url":"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/","name":"How my C# Object.Extend implementation works - Nerdhold Coder","isPartOf":{"@id":"http:\/\/www.nerdhold.com\/coder\/#website"},"datePublished":"2018-04-11T09:29:39+00:00","dateModified":"2019-06-07T10:40:40+00:00","author":{"@id":"http:\/\/www.nerdhold.com\/coder\/#\/schema\/person\/ca2988d5c0cb756a846e4d8c54e86b77"},"breadcrumb":{"@id":"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.nerdhold.com\/coder\/2018\/04\/11\/how-csharp-object-extend-works\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http:\/\/www.nerdhold.com\/coder\/"},{"@type":"ListItem","position":2,"name":"How my C# Object.Extend implementation works"}]},{"@type":"WebSite","@id":"http:\/\/www.nerdhold.com\/coder\/#website","url":"http:\/\/www.nerdhold.com\/coder\/","name":"Nerdhold Coder","description":"Tinkerings of a C# Coder","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"http:\/\/www.nerdhold.com\/coder\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"http:\/\/www.nerdhold.com\/coder\/#\/schema\/person\/ca2988d5c0cb756a846e4d8c54e86b77","name":"Lionell Pack","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"http:\/\/www.nerdhold.com\/coder\/#\/schema\/person\/image\/","url":"http:\/\/0.gravatar.com\/avatar\/9be7b23cd97814ac4a40b9b4d2955b5a?s=96&d=mm&r=pg","contentUrl":"http:\/\/0.gravatar.com\/avatar\/9be7b23cd97814ac4a40b9b4d2955b5a?s=96&d=mm&r=pg","caption":"Lionell Pack"},"sameAs":["http:\/\/blog.rophuine.net"],"url":"http:\/\/www.nerdhold.com\/coder\/author\/admin\/"}]}},"_links":{"self":[{"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/posts\/358"}],"collection":[{"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/comments?post=358"}],"version-history":[{"count":20,"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/posts\/358\/revisions"}],"predecessor-version":[{"id":463,"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/posts\/358\/revisions\/463"}],"wp:attachment":[{"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/media?parent=358"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/categories?post=358"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.nerdhold.com\/coder\/wp-json\/wp\/v2\/tags?post=358"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}