Grokking the DLR: Why it’s Not Just for Dynamic Languages (Cont.) Accessing the Dynamic Object as an Array When working with OData, lists of data are very common. To handle them well, you’ll want to add some functionality to your dynamic data classes. You may have noticed in Listing 3 that the DynamicOData class implemented the IEnumerable contract. This isn’t anything special required to enable dynamic typing. The code to do it is easy enough: public IEnumerator GetEnumerator() { foreach (var element in _current) yield return new DynamicOData(element); }
Just as I returned each named node in TryGetMember as a new DynamicOData object to make chaining possible, the iterator shown here wraps each XElement in the _current collection as a new DynamicOData object so that all of the nice dynamic language semantics we want to apply to the XML document extend to each node. Here’s a bit of test code that uses eBay’s OData feed to find the top ten items on their site pertaining to the same movie that we queried Netflix about. string ebayQueryFormat = "http://ebayodata.cloudapp.net/" + "Items?search={0}&$top=10"; string ebayUrl = String.Format( ebayQueryFormat, movieTitle); DynamicOData ebayItems = new DynamicOData(); ebayItems.OnDataReady += OnEbayItemsReady; ebayItems.FetchAsync(ebayUrl);
It’s the same pattern I used for fetching data from Netflix. I’m using the same DynamicOData type that I used to query Netflix. However, the query is a bit different since eBay provides a search verb to which the search term can be assigned. Listing 7 shows the OnEbayItemsReady method that is called when the data is loaded. The foreach loop shown in Listing 7 takes advantage of the IEnumerable implementation in my DynamicOData class. Inside that loop, since each returned item has been wrapped as a new DynamicOData instance, properties specific to the eBay OData feed like Id, Title and CurrentPrice become resolvable. Of course, if I wanted to ascribe array-like semantics directly to the DynamicOData class, I could do so by overriding TryGetIndex as follows: public override bool TryGetIndex( GetIndexBinder binder, object[] indexes, out object result) { int ndx = (int)indexes[0]; result = new DynamicOData( _current.ElementAt(ndx)); return true; }
This is a very simplistic implementation that assumes that my indexing strategy is purely numerical. Do you see the cast operation to coerce a single integer from the array? However, any sort of indexing I need is possible. The indexes parameter of the TryGetIndex method is an object array, meaning that C# compiler will pass exactly what’s provided by the caller. There may be one index value or a dozen of them. They could be strings or integers or even complex data types. The sky’s the limit as they say, so I’m free to get as creative as I like with the way in which the index parameters are implemented. Hopefully, the DynamicOData class I’ve shown here opens your eyes to the possibilities available to you when using the DLR. What I’ve created isn’t about dynamic languages per se. It’s true that C# and Visual Basic feels more dynamic when using a class that’s powered by the IDynamicMetaObjectProvider contract. But C# and Visual Basic are still statically-typed languages under the hood. Deferral of some binding operations until runtime gives them a feeling of being just dynamic enough to make our code more expressive than it’s ever been before. To finish up, let’s spend a bit of time discussing the performance concerns that arise from the code you’ve seen here. Rule and Call Site Caching The biggest concern that many developers have with dynamic programming languages is performance. The DLR goes to extraordinary measures to address that concern. I’ve touched briefly on the fact that the CallSite<T> class exists within the namespace called System.Runtime.CompilerServices. Also in that namespace are a number of other classes that perform caching at a variety of levels. Using these types, the DLR implements three major levels of caching to speed up dynamically dispatched operations: - A global rule cache
- A local rule cache
- A polymorphic delegate cache
The rule caches are used to avoid spending computing resources when binding objects by type for specific call sites. If two objects of type string are passed to a dynamic method and an integer is returned, global and/or local rule caches may be updated to record that pathway to the dynamic code. This can speed up binding in future calls. The delegate cache, which is managed within the call site object itself, is called polymorphic because the delegates stored there can take many shapes depending on code that’s encountered at runtime and the rules in the other caches used to generate them. As a runtime compiler service, the delegate cache is also sometimes referred to as inline. The reason for that term is that the expressions generated by the DLR and its binders are assembled into MSIL and Just-In-Time (JIT) compiled, just like any other .NET code. This runtime compilation happens in line with the normal flow and execution of your program. As you can imagine, turning dynamic code into compiled .NET code on the fly can make a massive, positive impact on the performance of the application. With the downloadable source code for this article, I’ve included a second project called PythonIntegration that interfaces some C# code to IronPython. I won’t cover the application here because it’s lengthy and would require a lot more space to describe. You’ll need to download and install IronPython if you want to experiment with the PythonIntegration application, of course. What you’ll discover is the vast difference between the static-to-dynamic language interoperability of the past compared to the high performance options offered by Microsoft’s DLR. Some over-the-border operations from C# to Python, measured in tight repetition, are literally 100,000 times faster using the caching mechanisms that you get for free when using the DLR. These same caching tactics are applied when calling from C# to any other CLR-compliant language, too. Conclusion The DLR isn’t just about dynamic languages. It opens up a whole world of possibilities for communicating between disparate systems. As .NET’s language of languages, the DLR enables the movement of code and data with a kind of fluidity and natural expressiveness that weren’t possible beforehand. As you've seen, the language of a data model like OData can be mapped rather generically into the syntax of C# and Visual Basic using the DLR, increasing comprehension dramatically. Other call invocation systems like Java’s Remote Method Invocation (RMI) can be mapped directly into our favorite languages as well, breathing life into existing code bases and increasing their overall business value. Because the DLR can shape the code and data of any other system into .NET so gracefully, the possibilities for using it should be limited only by your imagination. Kevin Hazzard | & | | CallSite Creation Optimization
Dynamic code has the potential of being slow so the DLR uses many performance techniques to speed things up. One of the simplest ideas is checking the existence of call sites as shown in Figure 2. Since the containers for call sites are static (Shared in Visual Basic), call sites only need to be created once, speeding up code that runs repeatedly and avoiding the expense altogether for dynamic code that may never run at all. |