Most .NET development is about working with strongly typed objects where the compiler knows in advance the properties and methods that a given class exposes. However, there are objects (and languages) out there that do not have a static structure against which you can program. Instead, they are designed to get their information at runtime based on data inside an HTML form, a text file, XML, a database, or something similar. These objects and languages are said to be dynamic in that they get their structure only at runtime. Dynamic support was added to .NET for the purpose of simplifying the access to dynamic application programming interfaces (APIs) provided by languages such as IronPython and IronRuby or even those found in Office Automation.
The C# language has a new data type called dynamic
. This type is similar to object
in that it might contain any actual type. In fact, in Visual Basic you simply use object
to get dynamic-like behavior. The difference in C#, however, is that any value defined as dynamic only has its actual type inferred at runtime (and not at compile time). This means you do not have type checking against valid methods. That is, the compiler does not stop you from writing code against methods or properties it cannot see at design time. Instead, type checking is only done when the code executes. Of course, this means that your dynamic type should be the right type at the right time or you get errors.
You can define dynamic fields, properties, variable, or return types of methods. For example, the following shows a property defined as a dynamic.
public dynamic DyProperty { get; set; }
At first glance, it would seem that the dynamic
keyword simply makes the type behave like types declared as objects. In fact, the differences are so slight that Visual Basic combines the concept of object and dynamic. However, in C#, the keyword dynamic
indicates that the property can contain any value and that no type checking is done at compile time regardless of what the code looks like that uses the property. That is in contrast to types declared as objects, in which the compiler evaluates expressions that use the type and prevents certain code (such as doing arithmetic with objects). Dynamic types do not get this scrutiny by the compiler and therefore either execute properly or throw an error if a problem exists.
Dynamics are useful for dealing with types and code outside of .NET, such as IronPython. However, you have to be careful when using them for your own needs. Because no resolution is done until runtime, you do not get strong type checking by the compiler or with IntelliSense. There is also a slight memory and performance penalty to pay at runtime for dynamic objects.
Figure 3.4 shows an example of the experience inside Visual Studio. There are two methods here. The first, VarTestMethod
, uses the var
statement to create an instance of the Employee
class. Notice an attempt to call the nonexistent property NewName
is type-checked as an error. The second method, DynTestMethod
, declares a dynamic
instance of Employee
. In this case, a call to emp.NewName
does not get a compile time error. The compiler allows this call, but an exception will be thrown at runtime if you get it wrong.
A dynamic object is one that gets its type information for things such as properties and methods at runtime. This is typically due to the fact that the object is meant to represent dynamic information such as that contained in an HTML or XML script file. In both cases, the underlying HTML and XML files you create are unique to your needs. Therefore, you cannot code directly against these models. Instead, you often have to code against static objects and write syntax such as MyXml.GetElement("EmployeeId")
. In this example, the GetElement
method then searches for the given XML element and returns the same. With a dynamic object, the object can be written to interrogate your XML (or similar data) and enables developers to code against the dynamic object as if it contained the EmployeeId
property. For example, developers could use your dynamic object to write their code as MyXml.EmployeeId
. The dynamic object still has to interrogate the underlying structure for an EmployeeId
, but this does simplify the coding for those working with your object and a dynamic structure such as XML or HTML.
You can create dynamic objects using either Visual Basic or C#. To do so, you inherit from the DynamicObject
class inside the System.Dynamic
namespace. You then override the members inside this class. These members serve as the basis for your dynamic items. For example, you can override the TrySetMember
and TryGetMember
to indicate the code that should be run when a user attempts to set or get a dynamic property on your object (such as calling MyXml.EmployeeId
). In this case, if a user is trying to return a dynamic property, the TryGetMember
method is called. Your code then determines how to return information for the dynamic property. (You might interrogate a file, for instance.)
There are many members on DynamicObject
for which you can provide functionality. In addition to the two aforementioned members, the other notables include TryInvokeMember
for invoking dynamic methods and TryCreateInstance
for creating new instances of a dynamic object.
You might also add your own methods and properties to a dynamic object. In this case, the dynamic object first looks for your property or method before calling out to the appropriate Try
member.
Let’s look at an example. Suppose that you were to write a dynamic object to represent an Employee
. In this case, perhaps you get data scraped from a web page or inside an XML file. You therefore want to convert this data to an object for easier programming. In this case, you can create a new class called Employee
and make sure it inherits from DynamicObject
. In our example, we use a simple Hashtable
of key value pairs to simulate the employee data. When a user creates an instance of this class, he is expected to pass the employee data to the dynamic class in the constructor. The skeleton of this class might then look like this.
C#
class Employee : System.Dynamic.DynamicObject
{
Hashtable _memberData;
public Employee(Hashtable employeeData)
{
_memberData = employeeData;
}
}
VB
Public Class Employee
Inherits System.Dynamic.DynamicObject
Dim _memberData As Hashtable
Public Sub New(ByVal employeeData As Hashtable)
_memberData = employeeData
End Sub
End Class
The next step is to override one or more of the Try
members of DynamicObject
to add our own functionality. In this simple example, we override the TryGetMember
method to provide functionality for reading a property and add it to the Employee
class created earlier. This method takes two parameters: binder
and result
. The binder
parameter is an object that represents the dynamic call made to your object (such as its name). The result
parameter is an outbound parameter of type object
. You use it to pass back any value you intend to pass as the property read. Finally, the method returns a bool
. This indicates true
if the member was determined to exist; otherwise, you return false
.
In the example, we simply look inside the Hashtable
for a given key (based on the binder.Name
property). If it exists, we set the result to its value and return true
. Otherwise, we set the result to null
and return false
. The following shows the code for this additional member of our Employee
class (assumes you’re using
[imports
in VB] System.Dynamic
).
C#
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
if (_memberData.ContainsKey(binder.Name))
{
//set the out parameter results to the value in the
// hash table for the given key
result = _memberData[binder.Name];
//indicate that member existed
return true;
}
else
{
//property does not exist in hash table
result = null;
return false;
}
}
VB
Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder,
ByRef result As Object) As Boolean
If _memberData.ContainsKey(binder.Name) Then
'set the out parameter results to the value in the
' hash table for the given key
result = _memberData(binder.Name)
'indicate that member existed
Return True
Else
'property does not exist in hash table
result = Nothing
Return False
End If
End Function
Note
Note that classes that inherit from DynamicObject
can be passed as instances to other languages that support the dynamic interoperability model. This includes IronPython and IronRuby.
You use a dynamic object like you would any other. You can create an instance, call methods and properties, and so on. However, you do not get type checking by the compiler. Again, this is because the object is late bound at runtime. In C#, you indicate a late-bound dynamic object using the keyword dynamic
. In Visual Basic, you simply declare your type as object
. Visual Basic figures out whether you are using late binding.
For example, suppose that you want to use the dynamic version of the Employee
class created in the previous section. Recall that this class simulates converting data into an object. In this case, the simulation is handled through a Hashtable
. Therefore, you need to declare an instance of the Employee
class as dynamic (or object in VB) and then create an instance passing in a valid Hashtable
. You can then call late-bound properties against your object. Recall that these properties are evaluated inside the TryGetMember
method you overrode in the previous example. The following shows a Console
application that calls the dynamic Employee
object.
C#
class Program
{
static void Main(string[] args)
{
Hashtable empData = new Hashtable();
empData.Add("Name", "Dave Elper");
empData.Add("Salary", 75000);
empData.Add("Title", "Developer");
dynamic dyEmp = new Employee(empData);
Console.WriteLine(dyEmp.Name);
Console.WriteLine(dyEmp.Salary);
Console.WriteLine(dyEmp.Title);
Console.WriteLine(dyEmp.Status);
Console.ReadLine();
}
}
VB
Module Module1
Sub Main()
Dim empData As New Hashtable()
empData.Add("Name", "Dave Elper")
empData.Add("Salary", 75000)
empData.Add("Title", "Developer")
Dim dyEmp As Object = New Employee(empData)
Console.WriteLine(dyEmp.Name)
Console.WriteLine(dyEmp.Salary)
Console.WriteLine(dyEmp.Title)
Console.WriteLine(dyEmp.Status)
Console.ReadLine()
End Sub
End Module
All this code passes the compiler’s test and executes accordingly. However, the last call to dyEmp.Status
is not valid. In this case, the dynamic object returns false
and thus throws an error. Figure 3.5 shows the results, including the Console output and the error message trying to access a bad member.
Tip
You can use the features discussed here to load a dynamic language library such as IronPython. In this case, you load the dynamic language library and can then use this library inside your code. For more on this, see “Creating and Using Dynamic Objects (C# and Visual Basic)” inside MSDN.