Chapter 18. Dynamic

Older versions of C# had trouble interacting with certain kinds of programs, especially those in the Microsoft Office family. You could get the job done, but before C# 4.0, it needed a lot of effort, and the results were ugly. The problem came down to a clash of philosophies: Office embraces a dynamic style, while C# used to lean heavily toward the static style. C# 4.0 now provides better support for the dynamic style, making it much easier to program Microsoft Office and similar systems from C#.

What exactly is the difference between static and dynamic? The terminology is slightly confusing because C# has a keyword called static which is unrelated, so you’ll need to put your knowledge of that static to one side for now. When it comes to the dynamic/static distinction, something is dynamic if it is decided at runtime, whereas a static feature is determined at compile type. If that sounds rather abstract, it’s because the distinction can apply to lots of different things, including the choice of which method to call, the type of a variable or an expression, or the meaning of an operator.

Let’s look at some concrete examples. The compiler is able to work out quite a lot of things about code during compilation, even code as simple as Example 18-1.

We’ve used the var keyword here, so we’ve not told the compiler what type these variables have, but it can work that out for us. The Console.ReadLine() method has a return type of string, meaning that myString must be of type string—the variable’s type can never be anything else, and so we say that it has a static type. (And obviously, the same would be true for any variable declared with an explicit type—declaring myString explicitly as a string would have changed nothing.) Likewise, the compiler is able to work out that modifiedString is also a string. Any variable declared with var will have a static type.

The compiler determines other aspects of code statically besides the types of variables. For example, there are method calls. The Console.ReadLine() call is straightforward. Console is a class name, so our code has been explicit about where to find the method. Since there’s no scope for ambiguity over which method we mean, this is a static method invocation—we know at compile time exactly which method will be called at runtime.

The myString.Replace method is slightly more interesting: myString refers to a variable, not a class, so to understand which method will be invoked, we need to know what type myString is. But as we already saw, in this example, its type is known statically to be string. As it happens, there are two overloads of Replace, one that takes two string arguments and one that takes two char arguments. In this code, we are passing to string literals, so the argument types are also known statically. This means that the compiler can work out which overload we require, and bakes that choice into the compiler output—once compilation completes, the exact method that Example 18-1 invokes is fixed. All the decisions are made at compile time here, and nothing can change the decision at runtime, and this is the nature of the static style.

Dynamic features defer decisions until runtime. For example, in a language that supports dynamic method invocation, the business of working out exactly which method to run doesn’t happen until the program gets to the point where it tries to invoke the method. This means that dynamic code doesn’t necessarily do the same thing every time it runs—a particular piece of code might end up invoking different methods from time to time.

You might be thinking that we’ve seen C# features in earlier chapters that enable this. And you’d be right: virtual methods, interfaces, and delegates all provide us with ways of writing code which picks the exact method to run at runtime. Static/dynamic is more of a continuum than a binary distinction. Virtual methods are more dynamic than nonvirtual methods, because they allow runtime selection of the method. Interfaces are more dynamic than virtual methods, because an object does not have to derive from any particular base class to implement a particular interface. Delegates are more dynamic than interfaces because they remove the requirement for the target to be compatible with any particular type, or even to be an object—whereas virtual methods and interfaces require instance methods, delegates also support those marked with the static keyword. (Again, try not to get distracted by the overlap in terminology here.) As you move through each of these mechanisms, the calling code knows slightly less about called code—there’s more and more freedom for things to change at runtime.

However, these mechanisms all offer relatively narrow forms of dynamism. The distinctions just listed seem rather petty next to a language that wholeheartedly embraces a dynamic style. JavaScript, for example, doesn’t even require the caller to know exactly how many arguments the method is expecting to receive.[49] And in Ruby, it’s possible for an object to decide dynamically whether it feels like implementing a particular method at all, meaning it can decide at runtime to implement methods its author hadn’t thought to include when originally writing the code!

Microsoft Office is programmable through a system called COM automation, which has an adaptable approach to argument counts. Office uses this to good effect. It offers methods which are remarkably flexible because they take an astonishing number of arguments, enabling you to control every conceivable aspect of the operation. The Office APIs are designed to be used from the Visual Basic for Applications (VBA) language, which uses a dynamic idiom, so it doesn’t matter if you leave out arguments you’re not interested in. Its dynamic method invocation can supply reasonable defaults for any missing values. But this leaves more statically inclined languages with a problem. C# 3.0 requires the number and type of arguments to be known at compile time (even with delegate invocation, the most dynamic form of method invocation available in that language). This means that you don’t get to leave out the parts you don’t care about—you are forced to provide values for every single argument.

So although the designers of Microsoft Word intended for you to be able to write code roughly like that shown in Example 18-2:

in C# 3.0 you would have been forced to write the considerably less attractive code shown in Example 18-3.

Not only has C# 3.0 insisted that we supply a value for every argument (using a special “this argument intentionally left blank” value to signify our intent to provide no particular value), but it has also insisted that we stick precisely to the rules of the type system. Word has chosen about the most general-purpose representation available to ensure maximum flexibility, which is why we see ref in front of every argument—it’s keeping open the possibility of passing data back out through any of these arguments. It doesn’t care that this gives the methods an unusually complex signature, because it just assumes that we’ll be using a language whose dynamic method invocation mechanism will automatically perform any necessary conversions at runtime. But if you’re using a language with no such mechanism, such as C# 3.0, it’s all rather unpleasant.

In fact, the way COM automation works is that the target object is ultimately responsible for dealing with defaults, coercion, and so on. The real problem is that C# 3.0 doesn’t have any syntax for exploiting this—if you want to defer to the COM object, you have to use the dynamic method invocation services provided by reflection, which were described in Chapter 17. Unfortunately, doing that from C# 3.0 looks even more unpleasant than Example 18-3.

Fortunately, C# 4.0 adds new dynamic features to the language that let us write code like Example 18-2, just as Word intended.



[49] Yes, so C# supports variable-length argument lists, but it fakes it. Such methods really have a fixed number of arguments, the last of which happens to be an array. There is only one variable-length Console.WriteLine method, and the compiler is able to determine statically when you use it.