Dynamic Types and the Dynamic Language Runtime - Programming with .NET Assemblies - C# 6.0 and the .NET 4.6 Framework (2015)

C# 6.0 and the .NET 4.6 Framework (2015)

PART V

image

Programming with .NET Assemblies

CHAPTER 16

image

Dynamic Types and the Dynamic Language Runtime

NET 4.0 introduced a new keyword to the C# language, specifically, dynamic. This keyword allows you to incorporate scripting-like behaviors into the strongly typed world of type safety, semicolons, and curly brackets. Using this loose typing, you can greatly simplify some complex coding tasks and also gain the ability to interoperate with a number of dynamic languages (such as IronRuby or IronPython), which are .NET savvy.

In this chapter, you will be introduced to the C# dynamic keyword and understand how loosely typed calls are mapped to the correct in-memory object using the Dynamic Language Runtime (DLR). After you understand the services provided by the DLR, you will see examples of using dynamic types to streamline how you can perform late-bound method calls (via reflection services) and to easily communicate with legacy COM libraries.

Image Note Don’t confuse the C# dynamic keyword with the concept of a dynamic assembly (see Chapter 18). While you could use the dynamic keyword when building a dynamic assembly, these are ultimately two independent concepts.

The Role of the C# dynamic Keyword

Back in Chapter 3, you learned about the var keyword, which allows you to define local variables in such a way that the underlying date type is determined at compile time, based on the initial assignment (recall that this is termed implicit typing). Once this initial assignment has been made, you have a strongly typed variable, and any attempt to assign an incompatible value will result in a compiler error.

To begin your investigation into the C# dynamic keyword, create a new Console Application project named DynamicKeyword. Now, author the following method in your Program class, and verify that the final code statement will indeed trigger a compile time error if uncommented:

static void ImplicitlyTypedVariable()
{
// a is of type List<int>.
var a = new List<int>();
a.Add(90);
// This would be a compile-time error!
// a = "Hello";
}

Using implicit typing simply for the sake of doing so is considered by some to be bad style (if you know you need a List<int>, just declare a List<int>). However, as you have seen in Chapter 12, implicit typing is useful with LINQ, as many LINQ queries return enumerations of anonymous classes (via projections) that you cannot directly declare in your C# code. However, even in such cases, the implicitly typed variable is, in fact, strongly typed.

On a related note, as you learned in Chapter 6, System.Object is the topmost parent class in the .NET Framework and can represent anything at all. Again, if you declare a variable of type object, you have a strongly typed piece of data; however, what it points to in memory can differ based on your assignment of the reference. To gain access to the members the object reference is pointing to in memory, you need to perform an explicit cast.

Assume you have a simple class named Person that defines two automatic properties (FirstName and LastName) both encapsulating a string. Now, observe the following code:

static void UseObjectVarible()
{
// Assume we have a class named Person.
object o = new Person() { FirstName = "Mike", LastName = "Larson" };

// Must cast object as Person to gain access
// to the Person properties.
Console.WriteLine("Person’s first name is {0}", ((Person)o).FirstName);
}

Since the release of .NET 4.0, the C# language introduced a keyword named dynamic. From a high level, you can consider the dynamic keyword a specialized form of System.Object, in that any value can be assigned to a dynamic data type. At first glance, this can appear horribly confusing, as it appears you now have three ways to define data whose underlying type is not directly indicated in your code base. For example, this method:

static void PrintThreeStrings()
{
var s1 = "Greetings";
object s2 = "From";
dynamic s3 = "Minneapolis";

Console.WriteLine("s1 is of type: {0}", s1.GetType());
Console.WriteLine("s2 is of type: {0}", s2.GetType());
Console.WriteLine("s3 is of type: {0}", s3.GetType());
}

would print out the following if invoked from Main():

s1 is of type: System.String
s2 is of type: System.String
s3 is of type: System.String

What makes a dynamic variable much (much) different from a variable declared implicitly or via a System.Object reference is that it is not strongly typed. Said another way, dynamic data is not statically typed. As far as the C# compiler is concerned, a data point declared with thedynamic keyword can be assigned any initial value at all and can be reassigned to any new (and possibly unrelated) value during its lifetime. Consider the following method and the resulting output:

static void ChangeDynamicDataType()
{
// Declare a single dynamic data point
// named "t".
dynamic t = "Hello!";
Console.WriteLine("t is of type: {0}", t.GetType());

t = false;
Console.WriteLine("t is of type: {0}", t.GetType());

t = new List<int>();
Console.WriteLine("t is of type: {0}", t.GetType());
}

t is of type: System.String
t is of type: System.Boolean
t is of type: System.Collections.Generic.List`1[System.Int32]

At this point in your investigation, do be aware that the previous code would compile and execute identically if you were to declare the t variable as a System.Object. However, as you will soon see, the dynamic keyword offers many additional features.

Calling Members on Dynamically Declared Data

Given that a dynamic variable can take on the identity of any type on the fly (just like a variable of type System.Object), the next question on your mind might be about calling members on the dynamic variable (properties, methods, indexers, register with events, etc.). Well, syntactically speaking, it will again look no different. Just apply the dot operator to the dynamic data variable, specify a public member, and supply any arguments (if required).

However (and this is a very big “however”), the validity of the members you specify will not be checked by the compiler! Remember, unlike a variable defined as a System.Object, dynamic data is not statically typed. It is not until runtime that you will know whether the dynamic data you invoked supports a specified member, whether you passed in the correct parameters, spelled the member correctly, and so on. Thus, as strange as it might seem, the following method compiles perfectly:

static void InvokeMembersOnDynamicData()
{
dynamic textData1 = "Hello";
Console.WriteLine(textData1.ToUpper());

// You would expect compiler errors here!
// But they compile just fine.
Console.WriteLine(textData1.toupper());
Console.WriteLine(textData1.Foo(10, "ee", DateTime.Now));
}

Notice the second call to WriteLine() attempts to call a method named toupper() on the dynamic data point (note the incorrect casing—it should be ToUpper() ). As you can see, textData1 is of type string, and therefore, you know it does not have a method of this name in all lowercase letters. Furthermore, string certainly does not have a method named Foo() that takes an int, string, and DataTime object!

Nevertheless, the C# compiler is satisfied. However, if you invoke this method from within Main(), you will get runtime errors similar to the following output:

Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
’string’ does not contain a definition for ’toupper’

Another obvious distinction between calling members on dynamic data and strongly typed data is that when you apply the dot operator to a piece of dynamic data, you will not see the expected Visual Studio IntelliSense. The IDE will allow you to enter any member name you could dream up.

It should make sense that IntelliSense is not possible with dynamic data. However, remember that this means you need to be extremely careful when you are typing C# code on such data points. Any misspelling or incorrect capitalization of a member will throw a runtime error, specifically an instance of the RuntimeBinderException class.

The Role of the Microsoft.CSharp.dll Assembly

When you create a new Visual Studio C# project, you will automatically have a reference set to an assembly named Microsoft.CSharp.dll (you can see this for yourself by looking in the References folder of the Solution Explorer). This library is small and defines only a single namespace (Microsoft.CSharp.RuntimeBinder) with two classes (see Figure 16-1).

image

Figure 16-1. The Microsoft.CSharp.dll assembly

As you can tell by their names, both of these classes are strongly typed exceptions. The most common class, RuntimeBinderException, represents an error that will be thrown if you attempt to invoke a member on a dynamic data type, which does not actually exist (as in the case of the toupper() and Foo() methods). This same error will be raised if you specify the wrong parameter data to a member that does exist.

Because dynamic data is so volatile, whenever you are invoking members on a variable declared with the C#dynamic keyword, you could wrap the calls within a proper try/catch block and handle the error in a graceful manner, like so:

static void InvokeMembersOnDynamicData()
{
dynamic textData1 = "Hello";

try
{
Console.WriteLine(textData1.ToUpper());
Console.WriteLine(textData1.toupper());
Console.WriteLine(textData1.Foo(10, "ee", DateTime.Now));
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex)
{
Console.WriteLine(ex.Message);
}
}

If you call this method again, you will find the call to ToUpper() (note the capital T and U) works correctly; however, you then find the error data displayed to the console.

HELLO
’string’ does not contain a definition for ’toupper’

Of course, the process of wrapping all dynamic method invocations in a try/catch block is rather tedious. As long as you watch your spelling and parameter passing, this is not required. However, catching exceptions is handy when you might not know in advance if a member will be present on the target type.

The Scope of the dynamic Keyword

Recall that implicitly typed data (declared with the var keyword) is possible only for local variables in a member scope. The var keyword can never be used as a return value, a parameter, or a member of a class/structure. This is not the case with the dynamic keyword, however. Consider the following class definition:

class VeryDynamicClass
{
// A dynamic field.
private static dynamic myDynamicField;

// A dynamic property.
public dynamic DynamicProperty { get; set; }

// A dynamic return type and a dynamic parameter type.
public dynamic DynamicMethod(dynamic dynamicParam)
{
// A dynamic local variable.
dynamic dynamicLocalVar = "Local variable";

int myInt = 10;

if (dynamicParam is int)
{
return dynamicLocalVar;
}
else
{
return myInt;
}
}
}

You could now invoke the public members as expected; however, as you are operating on dynamic methods and properties, you cannot be completely sure what the data type will be! To be sure, the VeryDynamicClass definition might not be useful in a real-world application, but it does illustrate the scope of where you can apply this C# keyword.

Limitations of the dynamic Keyword

While a great many things can be defined using the dynamic keyword, there are some limitations regarding its usage. While they are not showstoppers, do know that a dynamic data item cannot make use of lambda expressions or C# anonymous methods when calling a method. For example, the following code will always result in errors, even if the target method does indeed take a delegate parameter that takes a string value and returns void:

dynamic a = GetDynamicObject();

// Error! Methods on dynamic data can’t use lambdas!
a.Method(arg => Console.WriteLine(arg));

To circumvent this restriction, you will need to work with the underlying delegate directly, using the techniques described in Chapter 10. Another limitation is that a dynamic point of data cannot understand any extension methods (see Chapter 11). Unfortunately, this would also include any of the extension methods that come from the LINQ APIs. Therefore, a variable declared with the dynamic keyword has limited use within LINQ to Objects and other LINQ technologies.

dynamic a = GetDynamicObject();

// Error! Dynamic data can’t find the Select() extension method!
var data = from d in a select d;

Practical Uses of the dynamic Keyword

Given that dynamic data is not strongly typed, not checked at compile time, has no ability to trigger IntelliSense, and cannot be the target of a LINQ query, you are absolutely correct to assume that using the dynamic keyword just for the sake of doing so is a poor programming practice.

However, in a few circumstances, the dynamic keyword can radically reduce the amount of code you need to author by hand. Specifically, if you are building a .NET application that makes heavy use of late binding (via reflection), the dynamic keyword can save you typing time. As well, if you are building a .NET application that needs to communicate with legacy COM libraries (such as Microsoft Office products), you can greatly simplify your code base via the dynamic keyword. By way of a final example, web sites built using the MVC design pattern frequently use the ViewBag type, which can also be accessed in a simplified manner using the dynamic keyword.

Like any “shortcut,” you need to weigh the pros and cons. The use of the dynamic keyword is a trade-off between brevity of code and type safety. While C# is a strongly typed language at its core, you can opt in (or opt out) of dynamic behaviors on a call-by-call basis. Always remember that you never need to use the dynamic keyword. You could always get to the same end result by authoring alternative code by hand (and typically much more of it).

Image Source Code The DynamicKeyword project is located in the Chapter 16 subdirectory.

The Role of the Dynamic Language Runtime

Now that you better understand what “dynamic data” is all about, let’s learn how it is processed. Since the release of .NET 4.0, the Common Language Runtime (CLR) was supplemented with a complementary runtime environment named the Dynamic Language Runtime. The concept of a “dynamic runtime” is certainly not new. In fact, many programming languages such as JavaScript, LISP, Ruby, and Python have used it for years. In a nutshell, a dynamic runtime allows a dynamic language the ability to discover types completely at runtime with no compile-time checks.

If you have a background in strongly typed languages (including C#, without dynamic types), the notion of such a runtime might seem undesirable. After all, you typically want to receive compile-time errors, not runtime errors, wherever possible. Nevertheless, dynamic languages/runtimes do provide some interesting features, including the following:

· An extremely flexible code base. You can refactor code without making numerous changes to data types.

· A simple way to interoperate with diverse object types built in different platforms and programming languages.

· A way to add or remove members to a type, in memory, at runtime.

One role of the DLR is to enable various dynamic languages to run with the .NET runtime and give them a way to interoperate with other .NET code. Two popular dynamic languages that make use of the DLR are IronPython and IronRuby. These languages live in a dynamic universe, where type is discovered solely at runtime. And yet, these languages have access to the richness of the .NET base class libraries. Even better, their code bases can interoperate with C# (or vice versa), thanks to the inclusion of the dynamic keyword.

Image Note This chapter will not address how the DLR can be used to integrate with dynamic languages. However, you can find details at the IronPython (http://ironpython.codeplex.com) and IronRuby (http://rubyforge.org/projects/ironruby) web sites.

The Role of Expression Trees

The DLR makes use of expression trees to capture the meaning of a dynamic call in neutral terms. For example, when the DLR encounters some C# code, such as the following:

dynamic d = GetSomeData();
d.SuperMethod(12);

it will automatically build an expression tree that says, in effect, “Call the method named SuperMethod on object d, passing in the number 12 as an argument.” This information (formally termed the payload) is then passed to the correct runtime binder, which again could be the C# dynamic binder, the IronPython dynamic binder, or even (as explained shortly) legacy COM objects.

From here, the request is mapped into the required call structure for the target object. The nice thing about these expression trees (beyond that you don’t need to manually create them) is that this allows you to write a fixed C# code statement and not worry about what the underlying target actually is (COM object, IronPython, IronRuby code base, etc.). Figure 16-2 illustrates the concept of expression trees from a high level.

image

Figure 16-2. Expression trees capture dynamic calls in neutral terms and are processed by binders

The Role of the System.Dynamic Namespace

The System.Core.dll assembly includes a namespace named System.Dynamic. Truth be told, the chances are quite high that you will never need to use the types located here. However, if you were a language vendor, who wanted to enable their dynamic languages to interact with the DLR, you could make use of the System.Dynamic namespace to build a custom runtime binder.

Again, you won’t need to directly dig into the types of System.Dynamic in this book; however, feel free to check it out using the .NET Framework 4.6 SDK documentation if you are interested. For practical purposes, simply know that this namespace provides the necessary infrastructure to make a dynamic language “.NET aware.”

Dynamic Runtime Lookup of Expression Trees

As explained, the DLR will pass the expression trees to a target object; however, this dispatching will be influenced by a few factors. If the dynamic data type is pointing in memory to a COM object, the expression tree is sent to a low-level COM interface named IDispatch. As you might know, this interface was COM’s way of incorporating its own set of dynamic services. COM objects, however, can be used in a .NET application without the use of the DLR or C# dynamic keyword. Doing so, however (as you will see), tends to result in much more complex C# coding.

If the dynamic data is not pointing to a COM object, the expression tree may be passed to an object implementing the IDynamicObject interface. This interface is used behind the scenes to allow a language, such as IronRuby, to take a DLR expression tree and map it to Ruby specifics.

Finally, if the dynamic data is pointing to an object that is not a COM object and does not implement IDynamicObject, the object is a normal, everyday .NET object. In this case, the expression tree is dispatched to the C# runtime binder for processing. The process of mapping the expression tree to .NET specifics involves reflection services.

After the expression tree has been processed by a given binder, the dynamic data will be resolved to the real in-memory data type, after which the correct method is called with any necessary parameters. Now, let’s see a few practical uses of the DLR, beginning with the simplification of late-bound .NET calls.

Simplifying Late-Bound Calls Using Dynamic Types

One instance where you might decide to use the dynamic keyword is when you are working with reflection services, specifically when making late-bound method calls. In Chapter 15, you saw a few examples of when this type of method call can be useful, most commonly when you are building some type of extensible application. At that time, you learned how to use the Activator.CreateInstance() method to create an object, for which you have no compile-time knowledge of (beyond its display name). You can then make use of the types of theSystem.Reflection namespace to invoke members via late binding. Recall the following example from Chapter 15:

static void CreateUsingLateBinding(Assembly asm)
{
try
{
// Get metadata for the Minivan type.
Type miniVan = asm.GetType("CarLibrary.MiniVan");

// Create the Minivan on the fly.
object obj = Activator.CreateInstance(miniVan);

// Get info for TurboBoost.
MethodInfo mi = miniVan.GetMethod("TurboBoost");

// Invoke method ("null" for no parameters).
mi.Invoke(obj, null);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

While this is code works as expected, you might agree it is a bit clunky. Here, you have to manually make use of the MethodInfo class, manually query the metadata, and so forth. The following is a version of this same method, now using the C# dynamic keyword and the DLR:

static void InvokeMethodWithDynamicKeyword(Assembly asm)
{
try
{
// Get metadata for the Minivan type.
Type miniVan = asm.GetType("CarLibrary.MiniVan");

// Create the Minivan on the fly and call method!
dynamic obj = Activator.CreateInstance(miniVan);
obj.TurboBoost();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

By declaring the obj variable using the dynamic keyword, the heavy lifting of reflection is done on your behalf, courtesy of the DRL.

Leveraging the dynamic Keyword to Pass Arguments

The usefulness of the DLR becomes even more obvious when you need to make late-bound calls on methods that take parameters. When you use “longhand” reflection calls, arguments need to be packaged up as an array of objects, which are passed to the Invoke() method ofMethodInfo.

To illustrate using a fresh example, begin by creating a new C# Console Application project named LateBindingWithDynamic. Next, add a Class Library project to the current solution (using the File imageAdd image New Project menu option) named MathLibrary. Rename the initialClass1.cs file of the MathLibrary project to SimpleMath.cs, and implement the class like so:

public class SimpleMath
{
public int Add(int x, int y)
{
return x + y;
}
}

After you have compiled your MathLibrary.dll assembly, place a copy of this library in the \bin\Debug folder of the LateBindingWithDynamic project. (If you click the Show All Files button for each project of the Solution Explorer, you can simply drag and drop the file between projects.) At this point, your Solution Explorer should look something like Figure 16-3.

image

Figure 16-3. The LateBindingWithDynamic project has a private copy of MathLibrary.dll

Image Note Remember, the whole point of late binding is to allow an application to create an object for which it has no record of in the manifest. This is why you manually copied MathLibrary.dll into the output folder of the Console project, rather than reference the assembly using Visual Studio.

Now, import the System.Reflection namespace into the Program.cs file of your Console Application project. Next, add the following method to the Program class, which invokes the Add() method using typical reflection API calls:

private static void AddWithReflection()
{
Assembly asm = Assembly.Load("MathLibrary");
try
{
// Get metadata for the SimpleMath type.
Type math = asm.GetType("MathLibrary.SimpleMath");

// Create a SimpleMath on the fly.
object obj = Activator.CreateInstance(math);

// Get info for Add.
MethodInfo mi = math.GetMethod("Add");

// Invoke method (with parameters).
object[] args = { 10, 70 };
Console.WriteLine("Result is: {0}", mi.Invoke(obj, args));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

Now, consider the simplification of the previous logic with the dynamic keyword, via the following new method:

private static void AddWithDynamic()
{
Assembly asm = Assembly.Load("MathLibrary");

try
{
// Get metadata for the SimpleMath type.
Type math = asm.GetType("MathLibrary.SimpleMath");

// Create a SimpleMath on the fly.
dynamic obj = Activator.CreateInstance(math);

// Note how easily we can now call Add().
Console.WriteLine("Result is: {0}", obj.Add(10, 70));
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex)
{
Console.WriteLine(ex.Message);
}
}

Not too shabby! If you call both methods from the Main() method, you’ll see identical output. However, when using the dynamic keyword, you saved yourself quite a bit of work. With dynamically defined data, you no longer need to manually package up arguments as an array of objects, query the assembly metadata, or other such details. If you are building an application that makes heavy use of dynamic loading/late binding, I am sure you can see how these code savings would add up over time.

Image Source Code The LateBindingWithDynamic project is included in the Chapter 16 subdirectory.

Simplifying COM Interoperability Using Dynamic Data

Let’s see another useful case for the dynamic keyword within the context of a COM interoperability project. Now, if you don’t have much background in COM development, do be aware for this next example that a compiled COM library contains metadata, just like a .NET library; however, the format is completely different. Because of this, if a .NET program needs to communicate with a COM object, the first order of business is to generate what is known as an interop assembly (described in the following paragraphs). Doing so is quite straightforward. Just activate the Add Reference dialog box, select the COM tab, and find the COM library you want to use (see Figure 16-4).

image

Figure 16-4. The COM tab of the Add Reference dialog box will show you all registered COM libraries on your machine

Image Note Be aware that several important Microsoft object models (including Office products) are currently accessible only through COM interoperability. Thus, even if you do not have direct experience building COM applications, you might need to consume them from a .NET program.

Once you select a COM library, the IDE will respond by generating a new assembly that contains .NET descriptions of COM metadata. Formally speaking, these are termed interoperability assemblies (or simply, interop assemblies). Interop assemblies do not contain any implementation code, except for a small amount that helps translate COM events to .NET events. However, these interop assemblies are useful in that they shield your .NET code base from the complex underbelly of COM internals.

In your C# code, you can directly program against the interop assembly, allowing the CLR (and if you use the dynamic keyword, the DLR) to automatically map .NET data types into COM types, and vice versa. Behind the scenes, data is marshaled between the .NET and COM applications using a Runtime Callable Wrapper (RCW), which is basically a dynamically generated proxy. This RCW proxy will marshal and transform .NET data types into COM types and map any COM return values into .NET equivalents.

Figure 16-5 shows the big picture of .NET to COM interoperability.

image

Figure 16-5. .NET programs communicate with COM objects using a proxy termed the RCW

The Role of Primary Interop Assemblies

Many COM libraries created by COM library vendors (such as the Microsoft COM libraries that allow access to the object model of Microsoft Office products) provide an “official” interoperability assembly termed a primary interop assembly (PIA). PIAs are optimized interop assemblies, which clean up (and possibly extend) the code typically generated when referencing a COM library using the Add Reference dialog box.

PIAs are typically listed in the Assemblies section of the Add Reference dialog box (under the Extensions subarea). In fact, if you reference a COM library from the COM tab of the Add Reference dialog box, Visual Studio will not generate a new interoperability library as it would normally do but would use the provided PIA instead. Figure 16-6 shows the PIA of the Microsoft Office Excel object model, which you will be using in the next example.

image

Figure 16-6. PIAs are listed on the .NET tab of the Add Reference dialog box

Embedding Interop Metadata

Before the release of .NET 4.0, when a C# application made use of a COM library (PIA or not), you needed to ensure the client machine had a copy of the interop assembly on their computer. Not only did this increase the size of your application installer package, but the install script had to check that the PIA assemblies were indeed present and, if not, install a copy to the GAC.

However, under .NET 4.0 and higher, you can now elect to embed the interoperability data directly within your compiled .NET application. When you do so, you are no longer required to ship a copy of the interoperability assembly along with your .NET application, as the necessary interoperability metadata is hard-coded in the .NET program.

By default, when you select a COM library (PIA or not) using the Add References dialog, the IDE will automatically set the Embed Interop Types property of the library to True. You can see this setting first hand by selecting a referenced interop library in the References folder of the Solution Explorer and then investigating the Properties window (see Figure 16-7).

image

Figure 16-7. Interop assembly logic can be embedded directly into your .NET application

The C# compiler will include only the parts of the interop library you are actually using. Thus, if the real interop library has .NET descriptions of hundreds of COM objects, you will bring in only the definitions of the subset you are really using in your C# code. Beyond reducing the size of the application you need to ship to the client, you also have an easier installation path, as you don’t need to install any missing PIAs on the target machine.

Common COM Interop Pain Points

Let’s cover one more preliminary topic before the next example. Before the release of the DLR, when you authored C# code that used a COM library (via the interop assembly), you were sure to face a number of challenges. For example, many COM libraries defined methods that took optional arguments, which were not supported in C# until .NET 3.5. This required you to specify the value Type.Missing for every occurrence of the optional argument. For example, if a COM method took five arguments, all of which were optional, you would need to write the following C# code in order to accept the default values:

myComObj.SomeMethod(Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

Thankfully, you are now able to author the following simplified code, given that the Type.Missing values will be inserted at compile time if you don’t specify a specific value:

myComObj.SomeMethod();

On a related note, many COM methods provided support for named arguments, which as you recall from Chapter 4, allows you to pass values to members in any order you require. Given that C# supports this same feature, it is simply to “skip” over a set of optional arguments you don’t care about and set only the few you do.

Another common COM interop pain point has to do with the fact that many COM methods were designed to take and return a particular data type, termed the Variant. Much like the C# dynamic keyword, a Variant data type could be assigned to any type of COM data on the fly (strings, interface references, numerical values, etc.). Before you had the dynamic keyword, passing or receiving Variant data points required some hoop jumping, typically by way of numerous casting operations.

When you set the Embed Interop Types property to True, all COM Variant types are automatically mapped to dynamic data. This will not only reduce the need to extraneous casting operations when working with underlying COM Variant data types but will also further hide some COM complexities, such as working with COM indexers.

To showcase how C# optional arguments, named arguments, and the dynamic keyword all work together to simplify COM interop, you will now build an application that uses the Microsoft Office object model. As you work through the example, you will get a chance to use the new features, as well as forgo them, and then compare and contrast the workload.

Image Note If you do not have a background in Windows Forms, you might want to simply load the completed solution into Visual Studio and experiment with the code, rather than build this application by hand.

COM Interop Using C# Dynamic Data

Assume you have a Windows Forms GUI application (named ExportDataToOfficeApp), whose main window hosts a DataGridView control named dataGridCars. This same window has two Button controls, the first of which will bring up a custom dialog box to insert a new row of data to the grid, and the other of which will export the grid’s data to an Excel spreadsheet. Figure 16-8 shows the completed GUI.

image

Figure 16-8. The GUI of the COM interop example

The DataGridView control is filled with some initial data by handling the form’s Load event as so (the Car class used as the type parameter for the generic List<T> is a simple class in the project with Color, Make, and PetName properties):

public partial class MainForm : Form
{
List<Car> carsInStock = null;

public MainForm()
{
InitializeComponent();
}

private void MainForm_Load(object sender, EventArgs e)
{
carsInStock = new List<Car>
{
new Car {Color="Green", Make="VW", PetName="Mary"},
new Car {Color="Red", Make="Saab", PetName="Mel"},
new Car {Color="Black", Make="Ford", PetName="Hank"},
new Car {Color="Yellow", Make="BMW", PetName="Davie"}
};

UpdateGrid();
}

private void UpdateGrid()
{
// Reset the source of data.
dataGridCars.DataSource = null;
dataGridCars.DataSource = carsInStock;
}
}

The Click event for the Add New Entry to Inventory button will launch a custom dialog box to allow the user to enter new data for a Car object, and if the user clicks the OK button, the data is added to the grid (I won’t bother to show the code behind the dialog box here, so please see the provided solution for details). If you are following along, however, include the NewCarDialog.cs, NewCarDialog.designer.cs, and NewCarDialog.resx files into your project (all of which are part of the code download for this text). After you have done so, implement the Add button click hander on the main window, as so:

private void btnAddNewCar_Click(object sender, EventArgs e)
{
NewCarDialog d = new NewCarDialog();
if (d.ShowDialog() == DialogResult.OK)
{
// Add new car to list.
carsInStock.Add(d.theCar);
UpdateGrid();
}
}

The Click event handler for the Export Current Inventory to Excel button is the heart of this example. Using the Add Reference dialog box, add a reference to the Microsoft.Office.Interop.Excel.dll primary interop assembly (as shown previously in Figure 16-7). Add the following namespace alias to the form’s primary code file. Be aware that this is not mandatory to define an alias when interacting with COM libraries. However, by doing so, you have a handy qualifier for all the imported COM objects, which is handy if some of these COM objects have names that would clash with your .NET types.

// Create an alias to the Excel object model.
using Excel = Microsoft.Office.Interop.Excel;

Implement this button Click event hander to call a private helper function named ExportToExcel(), like so:

private void btnExportToExcel_Click(object sender, EventArgs e)
{
ExportToExcel(carsInStock);
}

Because you imported the COM library using Visual Studio, the PIA has been automatically configured so that the used metadata will be embedded into the .NET application (recall the role of the Embed Interop Types property). Therefore, all COM Variants are realized as dynamicdata types. Furthermore, you can use C# optional arguments and named arguments. This being said, consider the following implementation of ExportToExcel():

static void ExportToExcel(List<Car> carsInStock)
{
// Load up Excel, then make a new empty workbook.
Excel.Application excelApp = new Excel.Application();
excelApp.Workbooks.Add();

// This example uses a single workSheet.
Excel._Worksheet workSheet = excelApp.ActiveSheet;

// Establish column headings in cells.
workSheet.Cells[1, "A"] = "Make";
workSheet.Cells[1, "B"] = "Color";
workSheet.Cells[1, "C"] = "Pet Name";

// Now, map all data in List<Car> to the cells of the spreadsheet.
int row = 1;
foreach (Car c in carsInStock)
{
row++;
workSheet.Cells[row, "A"] = c.Make;
workSheet.Cells[row, "B"] = c.Color;
workSheet.Cells[row, "C"] = c.PetName;
}

// Give our table data a nice look and feel.
workSheet.Range["A1"].AutoFormat(
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);

// Save the file, quit Excel, and display message to user.
workSheet.SaveAs(string.Format(@"{0}\Inventory.xlsx", Environment.CurrentDirectory));
excelApp.Quit();
MessageBox.Show("The Inventory.xslx file has been saved to your app folder",
"Export complete!");
}

This method begins by loading Excel into memory; however, you won’t see it visible on your computer desktop. For this application, you are interested only in using the internal Excel object model. However, if you do want to actually display the UI of Excel, update your method with this additional line of code:

static void ExportToExcel(List<Car> carsInStock)
{
// Load up Excel, then make a new empty workbook.
Excel.Application excelApp = new Excel.Application();

// Go ahead and make Excel visible on the computer.
excelApp.Visible = true;
...
}

After you create an empty worksheet, you add three columns that are named similar to the properties of the Car class. Then, you fill the cells with the data of the List<Car> and save your file under the (hard-coded) name Inventory.xlsx.

At this point, if you run your application, add a few new records, and export your data to Excel, you will then be able to open the Inventory.xlsx file, which will be saved to the \bin\Debug folder of your Windows Forms application. Figure 16-9 shows a possible export.

image

Figure 16-9. Exporting your data to an Excel file

COM interop Without C# Dynamic Data

Now, if you were to select the Microsoft. Office.Interop.Excel.dll assembly (in Solution Explorer) and set its Embed Interop Type property to False, you would have new compiler errors, as the COM Variant data is no longer realized as dynamic data but asSystem.Object variables. This will require you to update ExportToExcel() with a number of explicit casting operations.

As well, if this project were compiled under .NET 3.5 or earlier, you would no longer have the benefit of optional/named parameters and would have to explicitly mark all missing arguments. Here is a version of the ExportToExcel() method that would be required in earlier versions of C# (do note the increased complexity in code):

static void ExportToExcel2008(List<Car> carsInStock)
{
Excel.Application excelApp = new Excel.Application();

// Must mark missing params!
excelApp.Workbooks.Add(Type.Missing);

// Must cast Object as _Worksheet!
Excel._Worksheet workSheet = (Excel._Worksheet)excelApp.ActiveSheet;

// Must cast each Object as Range object then
// call low-level Value2 property!
((Excel.Range)excelApp.Cells[1, "A"]).Value2 = "Make";
((Excel.Range)excelApp.Cells[1, "B"]).Value2 = "Color";
((Excel.Range)excelApp.Cells[1, "C"]).Value2 = "Pet Name";

int row = 1;
foreach (Car c in carsInStock)
{
row++;
// Must cast each Object as Range and call low-level Value2 prop!
((Excel.Range)workSheet.Cells[row, "A"]).Value2 = c.Make;
((Excel.Range)workSheet.Cells[row, "B"]).Value2 = c.Color;
((Excel.Range)workSheet.Cells[row, "C"]).Value2 = c.PetName;
}

// Must call get_Range method and then specify all missing args!
excelApp.get_Range("A1", Type.Missing).AutoFormat(
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2,
Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing);

// Must specify all missing optional args!
workSheet.SaveAs(string.Format(@"{0}\Inventory.xlsx", Environment.CurrentDirectory),
Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing);

excelApp.Quit();
MessageBox.Show("The Inventory.xslx file has been saved to your app folder",
"Export complete!");
}

Although the end result of running this program is identical, this version of the method is much more verbose, as I am sure you agree. That wraps up your look at the C# dynamic keyword and the DLR. I hope you can see how these features can simplify complex programming tasks and (perhaps more importantly) understand the trade-offs. When you opt into dynamic data, you do lose a good amount of type safety, and your code base is prone to many more runtime errors.

While there is certainly more to say about the DLR, this chapter has tried to focus on topics that are practical and useful in your day-to-day programming. If you want to learn more about advanced features of the Dynamic Language Runtime, such as integrating with scripting languages, be sure to consult the .NET Framework 4.6 SDK documentation (look up the topic “Dynamic Language Runtime Overview” to get started).

Image Source Code The ExportDataToOfficeApp project is included in the Chapter 16 subdirectory.

Summary

The dynamic keyword introduced in C# 4.0 allows you to define data whose true identity is not known until runtime. When processed by the new Dynamic Language Runtime, the automatically created “expression tree” will be passed to the correct dynamic language binder, where the payload will be unpackaged and sent to the correct object member.

Using dynamic data and the DLR, a number of complex C# programming tasks can be radically simplified, especially the act of incorporating COM libraries into your .NET applications. As you have also seen in this chapter, .NET 4.0 and higher provides a number of further simplifications to COM interop (which have nothing to do with dynamic data), such as embedding COM interop data into your applications, optional arguments, and named arguments.

While these features can certainly simplify your code, always remember that dynamic data makes your C# code much less type safe and open to runtime errors. Be sure you weigh the pros and cons of using dynamic data in your C# projects, and test accordingly!