About the book
This is a sample chapter
of the book C# in Depth, Second Edition.
It has been
published with
the exclusive
permission of Manning.
Written by: Jon Skeet
Pages: 500
Publisher: ManningISBN: 9781935182474
Get 30% discount
DotNetSlacker readers can get 30% off the full print book or ebook at www.manning.com
using the promo code
dns30 at checkout.
Dynamic Binding in a Static Language, Part 1 Series
Part 1 Dynamic Binding in a Static Language, Part 1
Part
2 Dynamic Binding in a Static Language, Part 2
Introduction
In the first part of this article, we looked at what it meant for code to be dynamic, and the
syntax involved in using dynamic typing in C# 4. In this part we're going to look at some examples of using it to interoperate with other platforms.
Dynamic typing is a
little bit like unsafe code, or interoperability with native code using P/Invoke. Many developers will have no need for it, or use it once in a blue moon. For other developers -
particularly those dealing with Microsoft Office - it will be give a huge productivity boost, either by making their existing code simpler or by allowing radically different
approaches to their problems.
This article is not meant to be exhaustive by any means, and I look forward to seeing innovative uses of dynamic typing from C# in the
coming years. Will unit testing and mocking take a big step forward with new frameworks? Will we see dynamic web service clients, accessing RESTful services with simple member
access? I'm not going to make any predictions, other than that it'll be an interesting area to keep can eye on.
We're going to look at two examples: working with Excel,
and calling into Python.
COM in general, and Microsoft Office in particular
If you choose to embed the interop types you're using into the assembly (by using the /link compiler switch, or setting the "Embed Interop Types" property to True) then
anything in the API which would otherwise be declared as object is changed to dynamic. This makes it much easier to work with somewhat weakly typed
APIs such as those exposed by Office. (Although the object model in Office is reasonably strong in itself, many properties are exposed as variants as they can deal with
numbers, strings, dates and so on.) I'll just show you just a short example here; the dynamic aspect is very easy to understand from just this one scenario. We're going to set
the first twenty cells of the top row of a new Excel worksheet to the numbers 1 to 20. Listing 1 shows an initial, statically typed piece of code to achieve this.
Listing 1: Setting a range of values with static typing
This time we've imported the Microsoft.Office.Interop.Excel namespace - so the Application type refers to Excel, not Word. We're still using the new
features of C# 4, by not specifying an argument for the optional parameter in the Workbooks.Add() call while we're setting things up and also by using a named
indexer . When Excel is up and running, we work out the start and end cells of our overall range. In this case they're both on the same row, but we could have created a
rectangular range instead by selecting two opposite corners. We could have created the range in a single call to Range["A1:T1"] but I personally find it easier to
work with numbers consistently. Cell names like B3 are great for humans, but harder to use in a program.
Once we've got the range, we set all the values in it by setting
the Value2 property with an array of integers . We can use a one-dimensional array as we're only setting a single row; to set a range spanning multiple rows we'd need to use a
rectangular array. This all works, but we've had to use three casts in six lines of code. The indexer we call via Cells and the ActiveSheet property
are both declared to return object normally. (Various parameters are also declared as type object, but that doesn't matter as much because
there's an implicit conversion from any non-pointer type to - it's only coming the other way object that requires the cast.) For simplicity's sake I haven't closed Excel at the
end of the listing - it's easier to just see the open worksheet than to save it to a file in code, close the application, and then load up the file separately to check that it's
worked.
With the Primary Interop Assembly set to embed the required types into our own binary, all of these examples become dynamic. With the implicit
conversion from to any other type, we can just remove all the casts, as shown in dynamic listing 2.
Listing 2: Using implicit conversions from dynamic in Excel
This really is exactly the same code as listing 14.5 but without the casts. However, it's worth noting that the conversions are still checked at execution time. If
we changed the declaration of start to be Worksheet, the conversion would fail and an exception would be thrown. Of course, you don't have to perform the
conversion. You could just leave everything as dynamic, as shown in listing 3:
Listing 3: Using dynamic everywhere
Which is clearer? Well, I'm an old-fashioned static typing fan, so I prefer the previous version. It states the types I expect on each line, so if there are any
problems I get to find out immediately rather than waiting until I try to use a value in a way which may not be supported. In terms of productivity when initially developing,
there are pros and cons both ways - using dynamic, I don't need to work out which particular type I really expect; I can just use the value and so long as all the
necessary operations are supported, I'm okay. On the other hand, using static typing I can see what's available at every stage via IntelliSense. We're still using dynamic typing
of course, to provide the implicit conversion to Worksheet and Range - we're just using it for one step at a time rather than wholesale. The change
from static typing to dynamic may not look like much to start with because the example is relatively simple - but as the complexity of the code increases, so does the
readability benefit of removing all those casts.
In some ways this has all been a blast from the past - COM is a relatively old technology. Now we're going to jump to
interoperating with something much more recent: IronPython.
Dynamic languages such as IronPython
In this section I'm only going to use IronPython as an example, but of course that's not the only dynamic language available for the DLR. It's arguably the most mature, but
there are already alternatives such as IronRuby and IronScheme. One of the stated aims of the DLR is to make it easier for budding language designers to create a working
language which has good interoperability with other DLR languages and the traditional .NET languages such as C#, as well as access to the huge .NET framework libraries.
Why would I want to use IronPython from C#?
There are many reasons one might want to interoperate with a dynamic language, just as it's been beneficial to interoperate with other managed languages from .NET's infancy.
It's clearly useful for a VB developer to be able to use a class library written in C# and vice versa - so why would the same not be true of dynamic languages? I asked Michael
Foord, the author of Iron Python in Action, to come up with a few ideas for using IronPython within a C# application. Here's his list:
- User scripting
- Writing a layer of your application in IronPython
- Using Python as a configuration language
- Using Python as a rules engine with rules stored as text (even in a database)
- Using a Python library such as feedparser
- Putting a live interpreter into your application for debugging
If you're still skeptical, you might want to consider that embedding a scripting language in a mainstream application is far from uncommon - indeed, Sid Meier's
Civilization IV computer game is scriptable with Python. This isn't just an afterthought for modifications, either - a lot of the core gameplay is written in Python: once they'd
built the engine the developers found it to be a more powerful development environment than they'd originally imagined.
NOTEOr way of life, depending on how you view the world and your level of addiction to playing the game.
For this article, I'm going to pick the single example of using Python as a configuration language. Just as with the COM example, I'm going to keep it very simple, but
hopefully it'll provide enough of a starting point for you to experiment more with it if you're interested.
Getting started: embedding "hello, world"
There are various types available if you want to host or embed another language within a C# application, depending on the level of flexibility and control you
want to achieve. We're only going to use ScriptEngine and ScriptScope, because our requirements are quite primitive. In our example we know we're
always going to use Python, so we can ask the IronPython framework to create a ScriptRuntime directly; in more general situations you can use a ScriptRuntime
to pick language implementations dynamically by name. More demanding scenarios may require you to work with ScriptHost and ScriptSource, as
well as using more of the features of the other types too.
Not content with merely printing "hello, world" once, our initial example will do so twice, first by
using text passed directly into the engine as a string, and then by loading a file called HelloWorld.py. Listing 4 shows everything you need.
Listing 4: Printing "hello, world" twice using Python embedded in C#
You may find this listing either quite dull or very exciting, both for the same reason. It's simple to understand, requiring very little explanation. It does very
little, in terms of actual output... and yet the fact that it is so easy to embed Python code into C# is a cause for celebration. True, our level of interaction is
somewhat minimal so far - but it really couldn't be much easier than this.
Python's many string literal formsThe Python file contains a single line: print "hello, world" - note the double quotes in the file compared with
the single quotes in the string literal we passed into engine.Execute(). Either would have been fine in either source. Python has various string literal
representations, including triple single quotes or triple double quotes for multi-line literals. I only mention this because it's very useful not to have to escape double quotes
any time you want to put Python code into a C# string literal.
The next type we need is ScriptScope, which will be crucial to our configuration script.
Storing and retrieving information from a ScriptScope
The execution methods we've used both have overloads with a second parameter - a scope. In its simplest terms, this can be regarded as a dictionary of names and values.
Scripting languages often allow variables to be assigned without any explicit declaration, and when this is done in the top level of a program (instead of in a function or
class) this usually affects a global scope. When a ScriptScope instance is passed into an execution method, that is the global scope for the script you've
asked the engine to execute. The script can retrieve existing values from the scope, and create new values, as shown in listing 5.
Listing 5: Passing information between a host and the hosted script using ScriptScope
I've embedded the Python source code into the C# code as a verbatim string literal rather than putting it in a file, so that it's easier to see all the code in
one place. I don't recommend that you do this in production code, partly because Python is sensitive to whitespace - reformatting the code in a seemingly-harmless way can make
it fail completely at execution time.
The SetVariable and GetVariable methods simply put values into the scope and fetch them out again in the
obvious way. They're declared in terms of object rather than dynamic, as you might have expected. However, GetVariable also allows you
to specify a type argument, which acts as a conversion request. This is not quite the same as just casting the result of the nongeneric method, as the latter just unboxes the
value - which means you need to cast it to exactly the right type. For example, we can put an integer into the scope, but retrieve it as a double:
The first call succeeds: we're explicitly telling GetVariable what type we want, so it knows to coerce the value appropriately. The second call will
throw an InvalidCastException, just as it would in any other situation where you try to unbox a value using the wrong type.
The scope can also hold functions
which we can retrieve and then call dynamically, passing arguments and returning values. The easiest way of doing this is to use the dynamic type, as shown in
listing 6.
Listing 6: Calling a function declared in a ScriptScope
Configuration files may not often need this ability, but it can be useful in other situations. For example, you could easily use Python to script a graph-drawing
program, by providing a function to be called on each input point. There are a number of situations in which it's useful to have some sort of "expression evaluator" running user
code entered at execution time, such as evaluating business rules for discounts, shipping costs and so on. It can be useful to be able to change these rules in text form without
having to recompile or redeploy binaries.
Putting it all together
Now that we can get values into our scope, we're essentially done. We could potentially wrap the scope in another object providing access via an indexer - or even access the
values dynamically using DynamicObject. The application code might look something like this:
The exact form of the Configuration type will depend on your application, but it's unlikely to be terribly exciting code. I've provided a sample
dynamic implementation in the full source, which allows you to retrieve values as properties and call functions directly too. Of course we're not limited to just using primitive
types in our configuration: the Python code could be arbitrarily complex, building collections, wiring up components and services and so forth. Indeed, it could perform a lot of
the roles of a normal Dependency Injection or Inversion of Control container.
The important thing is that we now have a configuration file which is active instead
of the traditional passive XML and .ini files. Of course, you could have embedded your own programming language into previous configuration files - but the result would probably
have been less powerful, and would have taken a lot more effort to implement. As an example of where this could be useful in a simpler situation than full dependency injection,
you might want to configure the number of threads to use for some background processing component in your application. You might normally use as many threads as you have
processors in the system, but occasionally reduce it in order to help another application run smoothly on the same system. The configuration file would simply change from
something like this:
To this:
This change wouldn't require the application to be rebuilt or redeployed - just edit the file and restart the application. Particularly smart applications could
even choose to reconfigure themselves on the fly. (I've usually found that this ability is more painful to implement than the extra value it brings, but in certain places it can
make a big difference. The ability to change logging levels either for a particular bit of code or even just a specific user who's having difficulties can make debugging much
easier.)
Other than executing functions, we haven't really looked at using Python in a particularly dynamic way. The full power of Python is available, and using the
type in your C# code you can take advantage of meta-programming and dynamic all the other dynamic features. The C# compiler is responsible for representing your code in an
appropriate fashion, and the script engine is responsible for taking that code and working out what it means for Python. Just don't feel you have to be doing anything
particularly clever for it to be worth embedding the script engine in your application. It's a simple step towards a more powerful application.
One caveat around dynamic
execution like this: if you're executing arbitrary code, particularly code entered by external users of the system, you should think very seriously about security, and quite
possibly run the script in some sort of sandboxed environment. Discussion of this topic is outside the scope of this article, but it needs to be considered carefully.
Conclusion
There's much more to dynamic typing of course - including uses within C# which don't have anything to do with interoperability. I hope this article has given you a taste of
the possibilities - have fun exploring the dynamic world in your own code!
Dynamic Binding in a Static Language, Part 1 Series
Part 1 Dynamic Binding in a Static Language, Part 1
Part
2 Dynamic Binding in a Static Language, Part 2
Get 30% discount
DotNetSlacker readers can get 30% off the full print book or ebook at www.manning.com
using the promo code
dns30 at checkout.
About Manning Publications
 |
Manning Publication publishes computer books for professionals--programmers, system administrators, designers, architects, managers and others. Our focus is on computing titles at professional levels. We care about the quality of our books. We work with our authors to coax out of them the best writi...
This author has published 33 articles on DotNetSlackers. View other articles or the complete profile here.
|
You might also be interested in the following related blog posts
Html Encoding Nuggets With ASP.NET MVC 2
read more
Dynamic in C# 4.0: Introducing the ExpandoObject
read more
My History of Visual Studio (Part 6)
read more
Dynamic in C# 4.0: Creating Wrappers with DynamicObject
read more
Fun With Method Missing and C# 4
read more
"Future Directions for Visual Basic" by Anders Hejlsberg and Jonathan Aneja
read more
Fun with C# 4.0s dynamic
read more
F# in VS2010
read more
My History of Visual Studio (Part 5)
read more
Dynamic Languages: A Separation of Concerns
read more
|
|
Please login to rate or to leave a comment.