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.
Example 18-1. Simple code with various static features
var myString = Console.ReadLine(); var modifiedString = myString.Replace("color", "colour");
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:
Example 18-2. Word automation as Microsoft intended
var doc = wordApp.Documents.Open("WordFile.docx", ReadOnly:true);
in C# 3.0 you would have been forced to write the considerably less attractive code shown in Example 18-3.
Example 18-3. Word automation before C# 4.0
object fileName = @"WordFile.docx"; object missing = System.Reflection.Missing.Value; object readOnly = true; var doc = wordApp.Documents.Open(ref fileName, ref missing, ref readOnly, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
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.