I’ve had a few people ask me to explain how my C# Object.Extend implementation works – 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 tried to chain two calls together: I immediately got a RuntimeBinderException
. Unfortunately, for a really good reason (the difficulty of discovering the right namespaces to search), the code responsible for runtime method binding doesn’t look for extension methods. That means that when the first call to .Extend
returns something marked as dynamic
(regardless of what actual type it returns), you can’t use any extension methods on the result. Attempting to call either .Extend
again or .Dump
(another common extension method in LINQPad) results in the dreaded RuntimeBinderException
.
The usual usage of the dynamic
keyword is to let us return something like the ExpandoObject
type, like this:
dynamic ReturnSomething() { return new ExpandoObject(); }
Fortunately, the dynamic
keyword in C# essentially just tells the compiler to engage duck-typing mode – it doesn’t actually tell the run-time anything. So there’s nothing stopping us from doing this:
dynamic ReturnSomething() { return new List<string>(); }
The compiler (and your IDE) will allow you to try to call anything at all on the return value of ReturnSomething
, and hand everything off to the runtime binder. This means that you can’t use extension methods on anything returned by a method marked dynamic
, even if you actually return a regular non-dynamic type.
dynamic ReturnSomething() { return new object(); } ReturnSomething().Dump();
Nope. Instant RuntimeBinderException
.
There’s one situation which will give us exactly what we want – if we mark the .Extend
function as dynamic
, but the type we return has our Extend
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 Extend
, and the runtime binder will be able to find the methods because they’re right on the type we return – we’re not relying on extension methods!
I initially thought I could create a subclass of ExpandoObject
with calls for Extend
and Dump
, but it turns out that not only is ExpandoObject
a sealed class, it’s also a bit special in other ways – so that was a no-go.
So now the only problem we have is that we need to create types on the fly which contain functions for Extend
(and Dump
, for convenience), and also whatever properties we want to dynamically add – 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 Microsoft.CSharp.CSharpCodeProvider
– 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’s very possible to use this to create types on the fly – containing both the method calls we want for Dump
and Extend
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 – and the runtime binder can find all of the properties, as well as our .Extend
and .Dump
methods, because they’re actually on the type the runtime binder is looking at.
The minimum viable code to do something useful with CSharpCodeProvider
looks something like this (note that this requires a couple of helpers you can find in the class linked below):
var compileUnit = new CodeCompileUnit(); var nameSpace = new CodeNamespace("MyDynamicNamespace"); var classType = new CodeTypeDeclaration("MyNewType"); classType.Attributes = MemberAttributes.Public; compileUnit.Namespaces.Add(nameSpace); nameSpace.Types.Add(classType); var returnThree = new CodeMemberMethod { Name = "ReturnThree", Attributes = MemberAttributes.Public, ReturnType = new CodeTypeReference(typeof(int)) }; returnThree.Statements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(3))); classType.Members.Add(returnThree); var result = _cSharpCodeProvider.Value.CompileAssemblyFromDom(_compilerParameters.Value, compileUnit); var compiledType = result.CompiledAssembly.GetTypes().Single(); LINQPad.Extensions.Dump(((dynamic)Activator.CreateInstance(compiledType)).ReturnThree());
This is a very round-about way to output the number 3, but it works!
I won’t reproduce the code here, but you can find all the details of the Object.Extend dynamic type creation in the CreateTypeUncached
function in ObjectExtend.cs.
You might notice the word ‘Uncached’ there. When I first tried this approach, it was horrifyingly slow – I was using Object.Extend in a Select
statement against a large IEnumerable
, 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.
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 on Twitter and let me know if parts of my explanation are hard to follow.