Couple of weeks ago I started to look into different open source packages on the .NET platform such as Nancy and Simple.Data. They all looked very cool, but one paragraph in an article called Introducing Simple.Data - explaining how the code snippet below works - caught my attention:
That’s pretty neat, right? So, did we have to generate the Database class and a bunch of table classes to make this work?
In this example, the type returned by Database.Open() is dynamic. It doesn’t have a Users property, but when that property is referenced on it, it returns a new instance of a DynamicTable type, again as dynamic. That instance doesn’t actually have a method called FindByNameAndPassword, but when it’s called, it sees “FindBy” at the start of the method, so it pulls apart the rest of the method name, combines it with the arguments, and builds an ADO.NET command which safely encapsulates the name and password values inside parameters. The FindBy* methods will only return one record; there are FindAllBy* methods which return result sets. This approach is used by the Ruby/Rails ActiveRecord library; Ruby’s metaprogramming nature encourages stuff like this.
Just read that sentence once again…
That instance doesn’t actually have a method called FindByNameAndPassword, but when it’s called, it sees “FindBy” at the start of the method, so it pulls apart the rest of the method name, combines it with the arguments, and builds an ADO.NET command which safely encapsulates the name and password values inside parameters.
This sounds like crazy awesome magic and I need to understand it!
How magic works?
To find out how the magic worked, I downloaded the source code from github and started to poke around the code. The rest of the article explains what I got out of this experiment.
The sample project I created is sort of the skeleton of what Simple.Data does on the inside. This is not an attempt to reproduce or fully understand the library. I just wanted to understand how one can call a non-existing method on a class and what is happening behind the curtain that enables this at runtime.
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9 10 11 12
I created a class that derives from DynamicObject and a method that returns an instance of this object, but instead of a return type I added the dynamic keyword to the method signature. What this does effectively is that when compiling my simple test code, the compiler will not look for the declaration of a Users property. It knows that this will resolved at runtime.
Once running this code there will be a RuntimeBinderException exception thrown with the the following message:
Now, we need to somehow create a Users property on the DynamicTest class. For this, we need to override the TryGetMember method of the DynamicObject and implement this behavior.
So, TryGetMember will just create and return an instance of an ObjectReference object. It has a parameter called binder that has information about the member that is called and an out parameter to pass back the result. The created object will be stored with binder.Name as key, so next time the same instance can be returned. This will represent the .User part of our dynamic call. But how will we handle the remaining method call?
1 2 3 4 5 6 7 8 9 10
ObjectReference is also derived from DynamicObject and it overrides the TryInvokeMember method to handle the remaining method call. Again, it receives a binder that contains information about the member that is being invoked, the parameters that is passed in and it has an out parameter that is used to pass back the result. What this method does is just simply prints out the method that is being invoked and its parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
Now, that I better understand how all this works I realized that I am way behind with the new features of C#. I guess, I need to buy a book about the last version of the language, read it and learn a bit more as I am behind. Below is the full sample code that I created:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80