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.
C# 4.0 introduces a new type called dynamic
. In some ways it
looks just like any other type such as int
, string
,
or FileStream
: you can use it in
variable declarations, or function arguments and return types, as Example 18-4 shows. (The method reads a little oddly—it’s a
static
method in the sense that it does
not relate to any particular object instance. But it’s dynamic in the
sense that it uses the dynamic
type for
its parameters and return value.)
Example 18-4. Using dynamic
static dynamic AddAnything(dynamic a, dynamic b) { dynamic result = a + b; Console.WriteLine(result); return result; }
While you can use dynamic
almost
anywhere you could use any other type name, it has some slightly unusual
characteristics, because when you use dynamic
, you are really saying “I have no idea
what sort of thing this is.” That means there are some situations where
you can’t use it—you can’t derive a class from dynamic
, for example, and typeof(dynamic)
will not compile. But aside from
the places where it would be meaningless, you can use it as you’d use any
other type.
To see the dynamic behavior in action, we can try passing in a few
different things to the AddAnything
method from
Example 18-4, as Example 18-5 shows.
Example 18-5. Passing different types
Console.WriteLine(AddAnything("Hello", "world").GetType().Name); Console.WriteLine(AddAnything(31, 11).GetType().Name); Console.WriteLine(AddAnything("31", 11).GetType().Name); Console.WriteLine(AddAnything(31, 11.5).GetType().Name);
AddAnything
prints the value it
calculates, and Example 18-5 then goes on to
print the type of the returned value. This produces the following
output:
Helloworld String 42 Int32 3111 String 42.5 Double
The +
operator in AddAnything
has behaved differently
(dynamically, as it were) depending on the type of data we provided it
with. Given two strings, it appended them, producing a string result.
Given two integers, it added them, returning an integer as the result.
Given some text and a number, it converted the number to a string, and
then appended that to the first string. And given an integer and a double,
it converted the integer to a double and then added it to the other
double.
If we weren’t using dynamic
,
every one of these would have required C# to generate quite different
code. If you use the +
operator in a
situation where the compiler knows both types are strings, it generates
code that calls the String.Concat
method. If
it knows both types are integers, it will instead generate code that
performs arithmetic addition. Given an integer and a double, it will
generate code that converts the integer to a double, followed by code to
perform arithmetic addition. In all of these cases, it would uses the
static information it has about the types to work out what code to
generate to represent the expression a +
b
.
Clearly C# has done something quite different with Example 18-4. There’s only one method, meaning it had to
produce a single piece of code that is somehow able to execute any of
these different meanings for the +
operator. The compiler does this by generating code that builds a special
kind of object that represents an addition operation, and this object then
applies similar rules at runtime to those the compiler would have used at
compile time if it knew what the types were. (This makes dynamic
very different from var
—see the sidebar on the next page.)
So the behavior is consistent with what we’re used to with C#. The
+
operator continues to mean all the
same things it can normally mean, it just picks the specific meaning at
runtime—it decides dynamically. The +
operator is not the only language feature capable of dynamic operation. As
you’d expect, when using numeric types, all the mathematical operators
work. In fact, most of the language elements you can use in a normal C#
expression work as you’d expect. However, not all operations make sense in
all scenarios. For example, if you tried to add a COM object to a number,
you’d get an exception. (Specifically, a RuntimeBinderException
, with a message
complaining that the +
operator cannot
be applied to your chosen combination of types.) A COM object such as one
representing an Excel spreadsheet is a rather different sort of thing from
a .NET object. This raises a question: what sorts of objects can we use
with dynamic
?
Not all objects behave in the same way when you use them
through the dynamic
keyword. C#
distinguishes between three kinds of objects for dynamic purposes: COM
objects, objects that choose to customize their dynamic behavior, and
ordinary .NET objects. We’ll see several examples of that second
category, but we’ll start by looking at the most important dynamic
scenario: interop with COM objects.
COM objects such as those offered by Microsoft Word or Excel get
special handling from dynamic
. It
looks for COM automation support (i.e., an implementation of the
IDispatch
COM interface) and uses
this to access methods and properties. Automation is designed to
support runtime discovery of members, and it provides mechanisms for
dealing with optional arguments, coercing argument types where
necessary. The dynamic
keyword
defers to these services for all member access. Example 18-6 relies on this.
Example 18-6. COM automation and dynamic
static void Main(string[] args) { Type appType = Type.GetTypeFromProgID("Word.Application"); dynamic wordApp = Activator.CreateInstance(appType); dynamic doc = wordApp.Documents.Open("WordDoc.docx", ReadOnly:true); dynamic docProperties = doc.BuiltInDocumentProperties; string authorName = docProperties["Author"].Value; doc.Close(SaveChanges:false); Console.WriteLine(authorName); }
The first two lines in this method just create an instance of
Word’s application COM class. The line that calls wordApp.Documents.Open
will end up using COM
automation to retrieve the Document
property from the application object, and then invoke the Open
method on the document object. That
method has 16 arguments, but dynamic
uses the mechanisms provided by COM
automation to offer only the two arguments the code has provided,
letting Word provide defaults for all the rest.
Although dynamic
is doing
some very COM-specific work here, the syntax looks like normal C#.
That’s because the compiler has no idea what’s going on here—it never
does with dynamic
. So the syntax
looks the same regardless of what happens at runtime.
If you are familiar with COM you will be aware that not all COM
objects support automation. COM also supports custom interfaces, which do not
support dynamic semantics—they rely on compile-time knowledge to work
at all. Since there is no general runtime mechanism for discovering
what members a custom interface offers, dynamic
is unsuitable for dealing with these
kinds of COM interfaces. However, custom interfaces are well suited to
the COM interop services described in Chapter 19. dynamic
was added to C# mainly because of
the problems specific to automation, so trying to use it with custom
COM interfaces would be a case of the wrong tool for the job. dynamic
is most likely to be useful for
Windows applications that provide some sort of scripting feature
because these normally use COM automation, particularly those that
provide VBA as their default scripting language.
Silverlight applications can run in the web browser,
which adds an important interop scenario: interoperability between C#
code and browser objects. Those might be objects from the DOM, or from
script. In either case, these objects have characteristics that fit
much better with dynamic
than with
normal C# syntax, because these objects decide which properties are
available at runtime.
Silverlight 3 used C# 3.0, so dynamic
was not available. It was still
possible to use objects from the browser scripting world, but the
syntax was not quite as natural. For example, you might have defined a
JavaScript function on a web page, such as the one shown
in Example 18-7.
Example 18-7. JavaScript code on a web page
<script type="text/javascript"> function showMessage(msg) { var msgDiv = document.getElementById("messagePlaceholder"); msgDiv.innerText = msg; } </script>
Before C# 4.0, you could invoke this in a couple of ways, both of which are illustrated in Example 18-8.
Example 18-8. Accessing JavaScript in C# 3.0
ScriptObject showMessage = (ScriptObject) HtmlPage.Window.GetProperty("showMessage"); showMessage.InvokeSelf("Hello, world"); // Or... ScriptObject window = HtmlPage.Window; window.Invoke("showMessage", "Hello, world");
While these techniques are significantly less horrid than the C#
3.0 code for COM automation, they are both a little cumbersome. We
have to use helper methods—GetProperty
, InvokeSelf
, or Invoke
to retrieve properties and invoke
functions. But Silverlight 4 supports C# 4.0, and all script objects
can now be used through the dynamic
keyword, as Example 18-9 shows.
Example 18-9. Accessing JavaScript in C# 4.0
dynamic window = HtmlPage.Window; window.showMessage("Hello, world");
This is a far more natural syntax, so much so that the second
line of code happens to be valid JavaScript as well as being valid C#.
(It’s idiomatically unusual—in a web page, the window
object is the global object, and so
you’d normally leave it out, but you’re certainly allowed to refer to
it explicitly, so if you were to paste that last line into script in a
web page, it would do the same thing as it does in C#.) So dynamic
has given us the ability to use
JavaScript objects in C# with a very similar syntax to what we’d use
in JavaScript itself—it doesn’t get much more straightforward than
that.
The Visual Studio tools for Silverlight do not automatically
add a reference to the support library that enables dynamic
to work. So when you first add a
dynamic
variable to a Silverlight
application, you’ll get a compiler error. You need to add a
reference to the Microsoft.CSharp
library in your Silverlight project. This applies only to
Silverlight projects—other C# projects automatically have a
reference to this library.
Although the dynamic
keyword was
added mainly to support interop scenarios, it is quite capable of
working with normal .NET objects. For example, if you define a class
in your project in the normal way, and create an instance of that
class, you can use it via a dynamic
variable. In this case, C# uses .NET’s reflection APIs to work out
which methods to invoke at runtime. We’ll explore this with a simple
class, defined in Example 18-10.
Example 18-10. A simple class
class MyType { public string Text { get; set; } public int Number { get; set; } public override string ToString() { return Text + ", " + Number; } public void SetBoth(string t, int n) { Text = t; Number = n; } public static MyType operator + (MyType left, MyType right) { return new MyType { Text = left.Text + right.Text, Number = left.Number + right.Number }; } }
We can use objects of this through a dynamic
variable, as Example 18-11 shows.
Example 18-11. Using a simple object with dynamic
dynamic a = new MyType { Text = "One", Number = 123 }; Console.WriteLine(a.Text); Console.WriteLine(a.Number); Console.WriteLine(a.Problem);
The lines that call Console.WriteLine
all use the dynamic
variable a
with normal C# property
syntax. The first two do exactly what you’d expect if the variable had
been declared as MyType
or var
instead of dynamic
: they just print out the values of
the Text
and Number
properties. The third one is more
interesting—it tries to use a property that does not exist. If the
variable had been declared as either MyType
or var
, this would not have compiled—the
compiler would have complained at our attempt to read a property that
it knows is not there. But because we’ve used dynamic
, the compiler does not even attempt
to check this sort of thing at compile time. So it compiles, and
instead it fails at runtime—that third line throws a RuntimeBinderException
, with a message
complaining that the target type does not define the Problem
member we’re looking for.
This is one of the prices we pay for the flexibility of dynamic
behavior: the compiler is less vigilant. Certain programming errors
that would be caught at compile time when using the static style do
not get detected until runtime. And there’s a related price:
IntelliSense relies on the same compile-time type information that
would have noticed this error. If we were to change the variable in Example 18-11’s type to either
MyType
or var
, we would see IntelliSense pop ups such
as those shown in Figure 18-1 while writing
the code.
Visual Studio is able to show the list of available methods
because the variable is statically typed—it will always refer to a
MyType
object. But with dynamic
, we get much less help. As Figure 18-2 shows, Visual
Studio simply tells us that it has no idea what’s available. In this
simple example, you could argue that it should be able to work it
out—although we’ve declared the
variable to be dynamic
, it can only
ever be a MyType
at this point in
the program. But Visual Studio does not attempt to perform this sort
of analysis for a couple of reasons. First, it would work for only
relatively trivial scenarios such as these, and would fail to work
anywhere you were truly exploiting the dynamic nature of dynamic
—and if you don’t really need the
dynamism, why not just stick with statically typed variables? Second,
as we’ll see later, it’s possible for a type to customize its dynamic
behavior, so even if Visual Studio knows that a dynamic
variable always refers to a MyType
object, that doesn’t necessarily mean
that it knows what members will be available at runtime. Another
upshot is that with dynamic
variables, IntelliSense provides the rather less helpful pop up shown
in Figure 18-2.
Example 18-11 just reads the properties, but as you’d expect, we can set them, too. And we can also invoke methods with the usual syntax. Example 18-12 illustrates both features, and contains no surprises.
Example 18-12. Setting properties and calling methods with dynamic
dynamic a = new MyType(); a.Number = 42; a.Text = "Foo"; Console.WriteLine(a); dynamic b = new MyType(); b.SetBoth("Bar", 99); Console.WriteLine(b);
Our MyType
example also
overloads the +
operator—it defines
what should occur when we attempt to add two of these objects
together. This means we can take the two objects from Example 18-12 and pass them to
the AddAnything
method
from Example 18-4, as Example 18-13 shows.
Recall that Example 18-4 just uses the
normal C# syntax for adding two things together. We wrote that code
before even writing the MyType
class, but despite this, it works just fine—it prints out:
FooBar, 141
The custom +
operator in
MyType
concatenates the Text
properties and adds the Number
properties, and
we can see that’s what’s happened here. Again, this shouldn’t really
come as a surprise—this is another example of the basic principle that
operations should work the same way when used through dynamic
as they would statically.
Example 18-13 illustrates
another feature of dynamic
—assignment. You can, of course,
assign any value into a variable of type dynamic
, but what’s more surprising is that
you can also go the other way—you are free to assign an expression of
dynamic
type into a variable of any
type. The first line of Example 18-13 assigns the return
value of AddAnything
into a variable of type
MyType
. Recall that AddAnything
has a return type of dynamic
, so you might have thought we’d need
to cast the result back to MyType
here, but we don’t. As with all dynamic operations, C# lets you try
whatever you want at compile time and then tries to do what you asked
at runtime. In this case, the assignment succeeds because AddAnything
ended up adding two MyType
objects together to return a
reference to a new MyType
object.
Since you can always assign a reference to a MyType
object into a MyType
variable, the assignment succeeds. If
there’s a type mismatch, you get an exception at runtime. This is just
another example of the same basic principle; it’s just a bit subtler
because assignment is usually a trivial operation in C#, so it’s not
immediately obvious that it might fail at runtime.
While most operations are available dynamically, there are a
couple of exceptions. You cannot invoke methods declared with the
static
keyword via dynamic
. In some ways, this is
unfortunate—it could be useful to be able to select a particular
static
(i.e., noninstance) method
dynamically, based on the type of object you have. But that would be
inconsistent with how C# works normally—you are not allowed to invoke
static
methods through a statically
typed variable. You always need to call them via their defining type
(e.g., Console.WriteLine
). The
dynamic
keyword does not change
anything here.
Extension methods are also not available through
dynamic
variables. On the one hand,
this makes sense because extension methods are really just static
methods disguised behind a convenient
syntax. On the other hand, that convenient syntax is designed to make
it look like these are really instance methods, as Example 18-14 shows.
Example 18-14. Extension methods with statically typed variables
using System.Collections.Generic; using System.Linq; class Program { static void Main() { IEnumerable<int> numbers = Enumerable.Range(1, 10); int total = numbers.Sum(); } }
The call to numbers.Sum()
makes
it look like IEnumerable<int>
defines a method called Sum
. In
fact there is no such method, so the compiler goes looking for
extension methods—it searches
all of the types in all of the namespaces for which we have provided
using
directives. (That’s why we’ve
included the whole program here rather than just a snippet—you need
the whole context including the using
System.Linq;
directive for that method call to make sense.)
And it finds that the Enumerable
type (in the System.Linq
namespace)
offers a suitable Sum
extension
method.
If we change the first line in the Main
method to the code shown in Example 18-15, things go
wrong.
The code still compiles, but at runtime, when we reach the call
to Sum
, it throws a RuntimeBinderException
complaining that the
target object does not define a method called Sum
.
So, in this case, C# has abandoned the usual rule of ensuring
that the runtime behavior with dynamic
matches what statically typed
variables would have delivered. The reason is that the code C#
generates for a dynamic call does not contain enough context. To
resolve an extension method, it’s necessary to know which using
directives are present. In theory, it
would have been possible to make this context available, but it would
significantly increase the amount of information the C# compiler would
need to embed—anytime you did
anything to a dynamic
variable, the
compiler would need to ensure that a list of all the relevant
namespaces was available. And even that wouldn’t be sufficient—at
compile time, C# only searches for extension methods in the assemblies
your project references, so to deliver the same method resolution
semantics at runtime that you get statically would require that
information to be made available too.
Worse, this would prevent the C# compiler from being able to optimize your project references. Normally, C# detects when your project has a reference to an assembly that your code never uses, and it removes any such references at compile time.[50] But if your program made any dynamic method calls, it would need to keep references to apparently unused assemblies, just in case they turn out to be necessary to resolve an extension method call at runtime.
So while it would have been possible for Microsoft to make this
work, there would be a significant price to pay. And it would probably
have provided only marginal value, because it wouldn’t even be useful
for the most widely used extension methods. The biggest user of
extension methods in the .NET Framework class library is LINQ—that
Sum
method is a standard LINQ
operator, for example. It’s one of the simpler ones. Most of the
operators take arguments, many of which expect lambdas. For those to
compile, the C# compiler depends on static type information to create
a suitable delegate. For example, there’s an overload of the Sum
operator that takes a lambda, enabling you to compute the sum of a value
calculated from the underlying data, rather than merely summing the
underlying data itself. Example 18-16 uses this
overload to calculate the sum of the squares of the numbers in the
list.
When the numbers
variable has
a static type (IEnumerable<int>
in our case) this
works just fine. But if numbers
is
dynamic
, the compiler
simply doesn’t have enough information to know what code it needs to
generate for that lambda. Given sufficiently heroic efforts from the
compiler, it could embed enough information to be able to generate all
the necessary code at runtime, but for what benefit? LINQ is designed
for a statically typed world, and dynamic
is designed mainly for interop. So
Microsoft decided not to support these kinds of scenarios with
dynamic
—stick with static typing
when using LINQ.
The dynamic
keyword uses an
underlying mechanism that is not unique to C#. It depends on a set of
libraries and conventions known as the DLR—the Dynamic Language
Runtime. The libraries are built into the .NET Framework, so these
services are available anywhere .NET 4 or later is available. This
enables C# to work with dynamic objects from other languages.
Earlier in this chapter, we mentioned that in the Ruby programming language, it’s possible to write code that decides at runtime what methods a particular object is going to offer. If you’re using an implementation of Ruby that uses the DLR (such as IronRuby), you can use these kinds of objects from C#. The DLR website provides open source implementations of two languages that use the DLR: IronPython and IronRuby (see http://dlr.codeplex.com/).
The .NET Framework class library includes a class called
ExpandoObject
, which is designed to be used through dynamic
variables. It chooses to customize
its dynamic behavior. (It does this by implementing a special
interface called IDynamicMetaObjectProvider
. This is defined
by the DLR, and it’s also the way that objects from other languages
are able to make their language-specific dynamic behavior available to
C#.) If you’re familiar with JavaScript, the idea behind ExpandoObject
will be familiar: you can set
properties without needing to declare them first, as Example 18-17 shows.
Example 18-17. Setting dynamic properties
dynamic dx = new ExpandoObject(); dx.MyProperty = true; dx.AnotherProperty = 42;
If you set a property that the ExpandoObject
didn’t previously have, it
just grows that as a new property, and you can retrieve the property
later on. This behavior is conceptually equivalent to a Dictionary<string, object>
, the only
difference being that you get and set values in the dictionary using
C# property accessor syntax, rather than an indexer. You can even
iterate over the values in an ExpandoObject
just as you would with a
dictionary, as Example 18-18
shows.
Example 18-18. Iterating through dynamic properties
foreach (KeyValuePair<string, object> prop in dx) { Console.WriteLine(prop.Key + ": " + prop.Value); }
If you are writing C# code that needs to interoperate with
another language that uses the DLR, this class can be
convenient—languages that fully embrace the dynamic style often use
this sort of dynamically populated object in places where a more
statically inclined language would normally use a dictionary, so
ExpandoObject
can provide a
convenient way to bridge the gap. ExpandoObject
implements IDictionary<string, object>
, so it
speaks both languages. As Example 18-19 shows, you add
properties to an ExpandoObject
through its dictionary API and then go on to access those as dynamic
properties.
Example 18-19. ExpandoObject as both dictionary and dynamic object
ExpandoObject xo = new ExpandoObject(); IDictionary<string, object> dictionary = xo; dictionary["Foo"] = "Bar"; dynamic dyn = xo; Console.WriteLine(dyn.Foo);
This trick of implementing custom dynamic behavior is not unique
to ExpandoObject
—we are free to write
our own objects that do the same thing.
The DLR defines an interface called IDynamicMetaObjectProvider
, and objects that
implement this get to define how they behave when used dynamically. It
is designed to enable high performance with maximum flexibility, which
is great for anyone using your type, but it’s a lot of work to
implement. Describing how to implement this interface would require a
fairly deep discussion of the workings of the DLR, and is beyond the
scope of this book. Fortunately, a more straightforward option
exists.
The System.Dynamic
namespace
defines a class called DynamicObject
. This implements IDynamicMetaObjectProvider
for you, and all
you need to do is override methods representing whichever operations
you want your dynamic object to support. If you want to support
dynamic properties, but you don’t care about any other dynamic
features, the only thing you need to do is override a single method,
TryGetMember
, as Example 18-20 shows.
Example 18-20. Custom dynamic object
using System; using System.Dynamic; public class CustomDynamic : DynamicObject { private static DateTime FirstSighting = new DateTime(1947, 3, 13); public override bool TryGetMember(GetMemberBinder binder, out object result) { var compare = binder.IgnoreCase ? StringComparer.InvariantCultureIgnoreCase : StringComparer.InvariantCulture; if (compare.Compare(binder.Name, "Brigadoon") == 0) { // Brigadoon famous for appearing only once every hundred years. DateTime today = DateTime.Now.Date; if (today.DayOfYear == FirstSighting.DayOfYear) { // Right day, what about the year? int yearsSinceFirstSighting = today.Year - FirstSighting.Year; if (yearsSinceFirstSighting % 100 == 0) { result = "Welcome to Brigadoon. Please drive carefully."; return true; } } } return base.TryGetMember(binder, out result); } }
This object chooses to define just a single property, called
Brigadoon
.[51] Our TryGetMember
will be called anytime
some code attempts to read a property from our object. The GetMemberBinder
argument provides the name
of the property the caller is looking for, so we compare it against
our one and only supported property name. The binder also tells us
whether the caller prefers a case-sensitive comparison—in C# IgnoreCase
will be false
, but some languages (such as VB.NET)
prefer case-insensitive comparisons. If the name matches, we then
decide at runtime whether the property should be present or not—this
particular property is available for only a day at a time once every
100 years. This may not be hugely useful, but it illustrates that
objects may choose whatever rules they like for deciding what
properties to offer.
If you’re wondering what you would get in exchange for the
additional complexity of IDynamicMetaObjectProvider
, it makes it
possible to use caching and runtime code generation techniques to
provide high-performance
dynamic operation. This is a lot more complicated than the simple
model offered by DynamicObject
,
but has a significant impact on the performance of languages in
which the dynamic model is the norm.
The main motivation behind dynamic
’s introduction
was to make it possible to use Office without writing horrible code. It
also has uses in other interop scenarios such as dealing with browser
script in Silverlight, and working with dynamic languages. But would you
ever use it in a pure C# scenario? The dynamic style has become
increasingly fashionable in recent years—some popular JavaScript libraries
designed for client-side web code make cunning use of dynamic idioms, as
do certain web frameworks. Some developers even go as far as to claim that
a dynamic style is inherently superior to a static style. If that’s the
way the wind is blowing, should C# developers follow this trend?
Tantalizingly, for those keen on dynamic languages, dynamic
has brought some dynamic language
features to C#. However, the key word here is some.
C# 4.0 added dynamic
to improve certain
interop scenarios, not to support whole new programming idioms. It is
therefore not helpful to think of dynamic
in terms of “dynamic extensions for
C#.”
If you attempt to use C# as though it were a fully fledged dynamic
language, you’ll be stepping outside the language’s core strength, so you
will inevitably run into problems. We’ve already seen a LINQ example that
did not mix well with dynamic
, and that
failure was a symptom of a broader problem. The underlying issue is that
delegates are not as flexible as you might expect when it comes to dynamic
behavior. Consider the method shown in Example 18-21.
We can use this in conjunction with the LINQ Where
operator, as Example 18-22 shows.
Example 18-22. Filtering with LINQ
var nums = Enumerable.Range(1, 200); var filteredNumbers = nums.Where(Test);
What if we wanted to make this more general-purpose? We could modify
Test
so that instead of working only
with int
, it works with any built-in
numeric type, or indeed any type that offers a version of the <
operator that can be used with int
. We could do that by changing the argument
to dynamic
, as Example 18-23 shows.
Unfortunately, this change would cause the code in Example 18-22 to fail with a compiler error. It
complains that there are no overloads that match delegate System.Func<int,bool>
, which is the
function type the Where
method expects
here. This is frustrating because our Test
method is certainly
capable of accepting an int
and
returning a bool
, but despite this, we
need to add our own wrapper. Example 18-24 does the job.
Example 18-24. Making a dynamic filter palatable for LINQ
var filteredNumbers = nums.Where(x => Test(x));
This is a little weird because it seems like it should mean exactly
the same as the equivalent line in Example 18-22.
We’ve had to add some extra code just to keep the C# type system happy,
and normally that’s exactly the sort of thing the dynamic style is
supposed to let you avoid. Part of the problem here is that we’re trying
to use LINQ, a thoroughly static-oriented API. But it turns out that
there’s a deeper problem here, which we can illustrate by trying to write
our own dynamic
-friendly version of
Where
. Example 18-25 will accept anything
as its test
argument. This DynamicWhere
method will
be happy as long as test
can be invoked
as a method that returns a bool
(or
something implicitly convertible to bool
).
Example 18-25. A dynamic-friendly Where implementation
static IEnumerable<T> DynamicWhere<T>(IEnumerable<T> input, dynamic test) { foreach (T item in input) { if (test(item)) { yield return item; } } }
This compiles, and will behave as intended if you can manage to invoke it, but unfortunately it doesn’t help. Example 18-26 tries to use this, and it will not compile.
Example 18-26. Attempting (and failing) to call DynamicWhere
var filteredNumbers = DynamicWhere(nums, Test); // Compiler error
The C# compiler complains:
Argument 2: cannot convert from 'method group' to 'dynamic'
The problem is that we’ve given it too much latitude. Example 18-25 will work with a wide
range of delegate types. It would be happy with Predicate<object>
, Predicate<dynamic>
,
Predicate<int>
, Func<object, bool>
, Func<dynamic, bool>
, or Func<int, bool>
. Or you could define a
custom delegate type of your own that was equivalent to any of these. The
only thing the C# compiler can see is that DynamicWhere
expects a dynamic
argument, so for all it knows, it could
pass any type at all. All it would have to do is pick one that fits the
Test
method’s signature—any delegate
type with a single argument and a return type of bool
would do. But it doesn’t have any rule to
say which particular delegate type to use by default here.
In Example 18-22, the compiler knew what to
do because the Where
method expected a
specific delegate type: Func<int,
bool>
. Since there was only one possible option, the C#
compiler was able to create a delegate of the right kind. But now that it
has too much choice, we need to narrow things down again so that it knows
what to do. Example 18-27 shows one way to
do this, although you could cast to any of the delegate types mentioned
earlier.
Example 18-27. Giving DynamicWhere a clue
var filteredNumbers = DynamicWhere(nums, (Predicate<dynamic>) Test);
Again, we’ve ended up doing extra work just to satisfy the C# type system, which is the opposite of what you’d usually expect in the dynamic idiom—types are supposed to matter less.
This is exactly the sort of problem you’ll run into if you attempt
to treat C# as a dynamic programming language—the underlying issue here is
that dynamic
was designed to solve
specific interop problems. It does that job very well, but C# as a whole
is not really at home in the dynamic style. So it’s not a good idea to
attempt to make heavy use of that style in your C# code.
C# 4.0’s new dynamic
keyword
makes it much easier to use objects that were designed to be used from
dynamic programming languages. In particular, COM automation APIs such as
those offered by the Microsoft Office suite are far more natural to use
than they have been in previous versions of the language. Interoperating
with browser script objects in Silverlight is also easier than
before.
[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.
[50] This optimization doesn’t occur for Silverlight projects, by the way. The way Silverlight uses control libraries from Xaml means Visual Studio has to be conservative about project references.
[51] According to popular legend, Brigadoon is a Scottish village which appears for only one day every 100 years.