Dynamic Types and Language Interoperability - Diving Deeper - Sams Teach Yourself C# 5.0 in 24 Hours (2013)

Sams Teach Yourself C# 5.0 in 24 Hours (2013)

Part V: Diving Deeper

Hour 22. Dynamic Types and Language Interoperability


What You’ll Learn in This Hour

Using dynamic types

Understanding the DLR

Interoperating with COM

Reflection interoperability


In Hour 1, “The .NET Framework and C#,” you learned that the fourth primary component of the .NET Framework is the dynamic language runtime, or DLR. The DLR is built on top of the common language runtime (CLR) and provides the language services for dynamic languages such as IronRuby and IronPython. Although the C# language has always allowed you to write code that would interact with dynamic languages—for example, calling a method defined in a JavaScript object—the syntax necessary to do so was far from simple. That syntax also changed depending on the particular dynamic language you were targeting.

With the help of the DLR, C# can provide simple and consistent language syntax for interacting with dynamic languages. In this hour, you learn about dynamic types and learn how you can use them to interoperate with dynamic languages.

Using Dynamic Types

A dynamic type, indicated by the dynamic keyword, is one whose operations bypass the compile-time type checking provided by the C# compiler. Instead, these operations are resolved at runtime. Bypassing the static type checking simplifies access to component object model (COM) application programming interfaces (APIs), such as the Office API, and to dynamic languages, such as IronPython and JavaScript.

The dynamic keyword indicates the type of a property, field, indexer, parameter, return value, or local variable. It can also be the destination type of an explicit conversion or as the argument to the typeof operator.


Note: Dynamic Types

Even though the term dynamic types is used throughout this hour, there is actually only a single “dynamic” type, just as there is a single “object” type. When used in this hour, dynamic types refers to different types that are treated as being of type dynamic.


Although it might seem contradictory, a variable of dynamic type is statically typed at compile time to be of type dynamic and, in most situations, behaves as if it were an object. When the compiler encounters a dynamic type, it assumes it can support any operation. If the code isn’t valid, the errors will be caught only at runtime.


Try It Yourself: Exploring Dynamic Types

To see how dynamic types behave at compile time and runtime, follow these steps:

1. Create a new console application in Visual Studio.

2. Add a new class named SimulatedDynamic that looks as shown here:

class SimulatedDynamic
{
public SimulatedDynamic() { }
public void Method1(string s)
{
Console.WriteLine(s);
}
}

3. Modify the Main method of the Program class by adding the statements shown here:

SimulatedDynamic c1 = new SimulatedDynamic();
c1.Method1(3);

4. Try to compile the application. You should receive the compiler errors shown in Figure 22.1.

Image

Figure 22.1. Error messages.

5. Change the first statement you entered in step 3 so that c1 is typed to be dynamic.

6. Run the application by pressing F5 (you want to run this under the debugger for this step). You should notice that the application now compiles but throws a runtime exception, as shown in Figure 22.2.

Image

Figure 22.2. RuntimeBinderException.

7. Change the second statement so that the argument passed is the string “3” rather than the integer value 3.

8. Run the application again by pressing Ctrl+F5 and observe that the output matches what is shown in Figure 22.3.

Image

Figure 22.3. Output of exploring dynamic types.


When you first tried to compile the application you just wrote, the compiler determined that the method you were attempting to execute did not exist because c1 was statically typed to be of type SimulatedDynamic. However, when you changed its static type to be of type dynamic, the compiler no longer performed the type checking, causing the runtime exception to occur.


Note: Determining Dynamic Types at Runtime

At compile time, a dynamic variable is compiled as an object type, and the compiler ensures that metadata about what each statement is attempting to do is stored. At runtime, this information is examined, and any statement that is not valid causes a runtime exception.

When the runtime encounters a “dynamic” type (which is actually just an object type with the additional metadata indicating it should be evaluated dynamically), it uses the runtime type of the object to determine what the actual type should be. In step 6 of the previous exercise, the runtime type of the method argument is System.Int32. This caused the runtime to attempt to resolve a method with the following signature:

void Method1(int)

Because such a method does not exist in the type SimulatedDynamic, a runtime exception is thrown. However, in step 8, the runtime attempts to resolve a method with the signature

void Method1(string)

that does exist.


Conversions

The result of most dynamic operations is another dynamic object. However, conversions exist between dynamic objects and other types, easily enabling you to switch between dynamic and nondynamic behavior.

An implicit conversion exists between all the predefined data types and dynamic, which means that all the following statements shown in Listing 22.1 are valid.

Listing 22.1. Implicit Conversion with Dynamic Types


dynamic d1 = 42;
dynamic d2 = "The quick brown fox";
dynamic d3 = DateTime.Now;

int s1 = d1;
string s2 = d2;
DateTime s3 = d3;



Caution: Dynamic Conversions

The implicit conversions that exist are dynamic conversions that, like all dynamic operations, are resolved at runtime. You should be careful and not mix dynamic and nondynamic conversions. For example, trying to assign d3 to s1 would result in a runtime exception.


Dynamic Overload Resolution

Because the actual type of a value of dynamic type is not known until runtime, if one or more method parameters are specified to be dynamic, the method might not be resolved until runtime. The compiler first attempts normal overload resolution, and, if an exact match is found, that is the overload that is used. This means that if more than one method resolves to match the same runtime types and the runtime cannot determine how to resolve that ambiguity, the method resolution fails with an ambiguous match exception.

Consider the code shown in Listing 22.2.

Listing 22.2. A Class with Dynamic Methods


class MethodResolution
{
public void M(int i) { }
public void M(int i, dynamic d) { }
public void M(dynamic d, int i) { }
public void M(dynamic d1, dynamic d2) { }
}


The following code will fail at runtime, as shown in Figure 22.4.

dynamic m = new MethodResolution();
m.M(42, 7);

Image

Figure 22.4. Ambiguous method call exception at runtime.

This method call is ambiguous because both arguments have a runtime type of System.Int32. When the runtime attempts to resolve method M, it sees these methods as if they were defined as shown in Listing 22.3.

Listing 22.3. Dynamic Method Definitions for Method Resolution


class MethodResolution
{
public void M(int i) { }
public void M(int i, object d) { }
public void M(object d, int i) { }
public void M(object d1, object d2) { }
}


Given that list of overloads, none of them are the best match because each matches better on one argument than the other. Because the method cannot be resolved, a runtime error is thrown.

Understanding the DLR

For C# to work with dynamic types, it makes use of the DLR, which is a runtime environment that sits on top of the CLR and provides language services and support for dynamic languages. This enables dynamic languages the same level of language interoperability enjoyed by the statically typed languages and introduces dynamic objects to those languages. Figure 22.5 shows the basic architecture of the DLR and shows how it fits with the rest of the .NET Framework.

Image

Figure 22.5. DLR architecture.

In a similar way to ADO.NET data providers, the DLR uses binders that encapsulate the language semantics and specify how operations are performed. This not only enables a consistent syntax but also enables statically typed languages to use the services provided by the DLR.

The language binders make use of LINQ expression trees to perform operations. To support this, the DLR extends LINQ expression trees to include control flow, assignment, and other language-specific nodes. Any location in code where you perform an operation on a dynamic object is considered a dynamic call site, and the DLR caches the metadata of the dynamic types involved and information about the operation being performed. If the operation is subsequently performed, the information is retrieved from the cache, improving performance.

Finally, to support interoperability with other languages, the DLR provides the IDynamicMetaObjectProvider interface and the DynamicObject and ExpandoObject classes.


Note: Understanding IDynamicMetaObjectProvider

The IDynamicMetaObjectProvider interface represents a dynamic object. If you have advanced scenarios for defining how dynamic objects behave, you should create your own implementation of IDynamicMetaObjectProvider; otherwise, you should derive from DynamicObjector use ExpandoObject.


If you need an object that enables you to dynamically add or remove members at runtime, you can create an instance of an ExpandoObject. Instances of an ExpandoObject can be shared between any languages that support the DLR. For example, you can create an instance in C# and use it as an argument to an IronPython function. Listing 22.4 shows a simple example of using an ExpandoObject to dynamically add a new property.

Listing 22.4. Dynamically Adding a New Property to an ExpandoObject


dynamic expando = new ExpandoObject();

expando.ExampleProperty = "This is a dynamic property.";
Console.WriteLine(expando.ExampleProperty);
Console.WriteLine(expando.ExampleProperty.GetType());


Figure 22.6 shows the output from the code shown in Listing 22.4.

Image

Figure 22.6. Output of using an ExpandoObject.

If you need to define specific operations for your class when it is used dynamically, you can derive your class from DynamicObject. Just as with an ExpandoObject instance, any languages supporting the DLR can share an instance of a derived DynamicObject type.

The code in Listing 22.5 shows a dynamic dictionary that enables you to access dictionary values by key as if they were actual properties. It overrides the TrySetMember and TryGetMember methods to provide this dynamic syntax.

Listing 22.5. Creating a Custom Dynamic Object


public class CustomDictionary : DynamicObject
{
Dictionary<string, object> internalDictionary =
new Dictionary<string, object>();

public int Count
{
get
{
return internalDictionary.Count;
}
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return this.internalDictionary.TryGetValue(binder.Name.ToLower(),
out result);
}

public override bool TrySetMember(SetMemberBinder binder, object value)
{
this.internalDictionary[binder.Name.ToLower()] = value;
return true;
}
}


Listing 22.6 shows how this new dictionary might be used.

Listing 22.6. Using a Custom Dynamic Object


public class Program
{
static void Main(string[] args)
{
dynamic contact = new CustomDictionary();
contact.FirstName = "Ray";
contact.LastName = "Manzarek";

Console.WriteLine(contact.FirstName + " " + contact.LastName);
}
}


The output from the code shown in Listing 22.6 is shown in Figure 22.7.

Image

Figure 22.7. Output of using a custom dynamic object.


Try It Yourself: Creating a Custom Dynamic Type

To implement a custom dynamic dictionary and see how to use it at runtime, follow these steps:

1. Create a new console application in Visual Studio.

2. Add a new class named CustomDictionary that looks like Listing 22.5.

3. Modify the Main method of the Program class so it looks like Listing 22.6.

4. Run the application by pressing Ctrl+F5. The output should match what is shown in Figure 22.7.

5. Add additional dynamically defined properties to the contact class and output them in Console.WriteLine statements.

6. Run the application again by pressing Ctrl+F5 and observe that the output changes to show the additional properties added.


Interoperating with COM

Although the .NET Framework has always enabled you to interoperate with COM, particularly with the Office COM APIs, the DLR simplifies the code you have to write considerably. Because many COM methods return types as object, the dynamic type enables you to easily treat them as dynamic objects instead.

To interoperate with COM APIs, you make use of a Primary Interop Assembly (PIA), which forms a bridge between the unmanaged COM API and the managed .NET application. To do this, you add a reference to the PIA in your Visual Studio project, allowing your application access to the types defined. When the reference has been added, you can choose to embed only those types actually used by your application.

The code in Listing 22.7 shows a simple example of how to open a Microsoft Word document using COM interop.


Note: Embedded Primary Interop Assemblies

Because each PIA must contain a managed equivalent for every type, interface, enumeration, and so on defined in the COM API, some, such as the Office PIAs, can be quite large.

Only embedding the interop types used by your application can help reduce the size of your deployment and does not require the PIAs to be deployed at all. This also allows you to avoid a lot of the complexity of working with COM APIs by treating the object occurrences in the COM signatures as dynamic instead.


Listing 22.7. COM Interop Without Dynamic Types


public static Main(string[] args)
{
object missing = System.Reflection.Missing.Value;
object readOnly = false;
object isVisible = true;
object fileName = "SampleDocument.docx";

Microsoft.Office.Interop.Word.ApplicationClass wordApp =
new Microsoft.Office.Interop.Word.ApplicationClass();
wordApp.Visible = true;

Document 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 isVisible, ref missing, ref missing,
ref missing, ref missing);

doc.Activate();
}


Compare that with the code shown in Listing 22.8, which performs the same actions but uses dynamic instead. This code also makes use of named parameters and implicit typing, showing how all these features come together, allowing you to write succinct code that is both easy to read and easy to maintain.

Listing 22.8. COM Interop with Dynamic Types


public static Main(string[] args)
{
var wordApp = new Microsoft.Office.Interop.Word.ApplicationClass();
wordApp.Visible = true;
string fileName = "SampleDocument.docx";

Document doc = wordApp.Documents.Open(fileName, ReadOnly: true, Visible: true);
doc.Activate();
}


Reflection Interoperability

Taking this a step further, suppose you wanted to invoke the Multiply method of a Calculator object. If the Calculator object was defined in C#, you could do this easily, as shown in Listing 22.9.

Listing 22.9. Invoking a Method in a C# Object


var calculator = GetCalculator();
int sum = calculator.Multiply(6, 7);


If you didn’t know the Calculator object was written in .NET, or needed to use Reflection because the compiler couldn’t determine that the object implemented a Multiply method, you would need to use code like that shown in Listing 22.10.

Listing 22.10. Invoking a Method Reflectively


object calculator = GetCalculator();
Type type = calculator.GetType();
object result = type.InvokeMember("Multiply", BindingFlags.InvokeMethod, null,
calculator, new object[] { 6, 7 });
int sum = Convert.ToInt32(result);


As you can see, this is not nearly as simple. However, by utilizing dynamic types, the code becomes that shown in Listing 22.11.

Listing 22.11. Invoking a Method Dynamically


dynamic calculator = GetCalculator();
int sum = calculator.Multiply(6, 7);


If the Calculator object were defined in a dynamic language such as IronPython, the same code shown in Listing 22.11 could still be used. The only differences would be in the implementation of the GetCalculator method, which would need to create an instance of the IronPython runtime and load the appropriate file, as shown in Listing 22.12.

Listing 22.12. Getting a Dynamic Object from IronPython


dynamic GetCalculator()
{
ScriptRuntime pythonRuntime = Python.CreateRuntime();
return pythonRuntime.UseFile("calculator.py");
}


Summary

Including the dynamic language runtime (DLR) greatly expands the number of languages the .NET Framework can support. It also enables statically typed languages such as C# to easily access objects defined in a dynamic language. Using dynamic types allows you to access dynamic objects using code syntax that is essentially the same as you would use if the object were statically defined.

In this hour, you learned the basics of using dynamic types, including how to use ExpandoObject and create your own custom dynamic objects by deriving from DynamicObject. After exploring the basic architecture of the DLR, you then learned how to use dynamic types when working with COM APIs, such as the Office COM API, and objects defined in other languages, such as IronPython.

Q&A

Q. What is a dynamic type?

A. A dynamic type is one whose operations bypass the compile-time type checking and instead are resolved at runtime.

Q. What is the benefit of ExpandoObject?

A. ExpandoObject enables you to create an instance of an object that enables you to dynamically add or remove members at runtime.

Q. Can you create your own dynamic objects?

A. Yes, you can create your own dynamic object by deriving from DynamicObject or implementing IDynamicMetaObjectProvider.

Q. What are the benefits of embedding primary interop assembly types?

A. Embedding only the interop types used by your application can help reduce the size of your deployment and does not require the PIAs to be deployed at all. This also allows you to avoid a lot of the complexity of working with COM APIs by treating the object occurrences in the COM signatures as dynamic instead.

Workshop

Quiz

1. What is the static compile-time type of a dynamic type?

2. What LINQ functionality does the DLR use to perform dynamic operations?

Answers

1. A dynamic type is statically typed at compile time to be of type dynamic.

2. The DLR language binders utilize LINQ expression trees to perform dynamic operations.

Exercises

There are no exercises for this hour.