C H A P T E R  5

images

Dynamic Objects

Dynamic objects are a very important part of the DLR. So far in this book, when I’ve needed dynamic objects in an example, I obtained them from IronPython or IronRuby. Now I’ll show you how you can implement your own custom late-binding logic in dynamic objects. Once you’re in control of a dynamic object’s late binding behavior, many magical things can happen. As I’ll demonstrate, dynamic objects let you provide a fluent API for constructing XML documents. Moreover, the same technique can be applied to accessing files, registry entries, and so forth. In Chapter 7, we’ll use dynamic objects to implement an aspect-oriented programming framework that works across both static and dynamic languages. And Chapter 8 will show you how to use dynamic objects to do meta-programming in C#, much like meta-classes work in languages like Python, Ruby and Groovy.

Expressions are the backbone of the DLR, and dynamic objects are the heart and soul. The heart and soul, of course, depend on the backbone. In the previous two chapters, you saw how we use expressions to express the late-binding logic in binders. Now you’ll see how we use them to express the late-binding logic in dynamic objects. As we go through this chapter, we will not only get to know dynamic objects, we’ll also put to good use what we learned about expressions in Chapter 3. Let’s start with a review of what it means for an object to be dynamic, and how such an object differs from a static object.

What is a Dynamic Object?

Here, dynamic, as always, means doing things at run time. I explained run time vs. compile time as well as dynamic vs. static in Chapter 1. Now the key question is, what is it that dynamic objects do at run time? The answer is late binding. Dynamic objects are essentially objects that know how to do their own late binding. I already mentioned this and we saw some examples in the previous chapter. Furthermore, I also mentioned that the DLR defines twelve actions, such as InvokeMember and GetMember, that can be late bound. So, for example, if some client code calls a method on a dynamic object, the dynamic object has the logic in it that knows how to bind the method call at run time. If some client code tries to access a property on a dynamic object, the dynamic object knows how to bind the property access at run time. All of this just recaps what we discussed in the previous chapter.

If we compare a dynamic object to a static object, the difference is obvious. In C#, when you call a method on a static object, the object itself does not know how to bind the method call; the C# compiler does. The C# compiler at compile time knows the types of all the parameters that flow into the method call and uses that information to find out which method of which class to bind the method call to. If the method is overloaded, the C# compiler will perform method overload resolution. If the method is an extension method, the C# compiler will do the job of locating the right extension method.

You’ll find it easier to understand the details about dynamic objects once you’ve seen one in action. So let’s look at the C# example of a dynamic object in Listing 5-1. The dynamic object’s late binding logic is trivial—always returning the constant number 3. Let’s see the effects of using the dynamic object, then I’ll show you how to implement its late-binding logic.

The dynamic object is an instance of a class called Customer. The customer name in this example is Bob and he is 30 years old. Because the code declares the type of the variable bob as dynamic, the variable bob is treated as a dynamic object by the C# compiler. When the code accesses the Age property of bob, the custom late binding logic of the dynamic object bob kicks in and the number 3 is returned as the result. In this example, I purposely make the late binding logic return the number 3 as the age of a customer, no matter what value the customer’s Age property has so that when we print out a customer’s age, it’s apparent whether that age comes from the customer’s Age property or from the late-binding logic. You won’t see the late-binding logic in Listing 5-1. You’re looking at just the tip of the iceberg now. Keep reading. Listings 5-2 and 5-3 (a little further on) fill out the example. It is in Listing 5-3 that you’ll eventually see the code that returns the value 3. You can find the code for the complete example in the DynamicObjectExamples project of this chapter’s code download.

Even though the Customer class implements its late-binding logic, nothing stops us from referring to a Customer object as static object. That’s what the code does with the variable bill in Listing 5-1. The example creates an instance of Customer and assigns it to the variable bill. The point here is that the type of the variable bill is Customer, not dynamic. Because of that, the C# compiler will not generate IL code that has call sites and binders for any action on bill. The variable bill (I mean the object the variable bill points to, but for brevity, I’ll just say the variable bill) is treated as a static object, just like any static object we see in C# code. So when the code gets the Age property of bill, there is no late binding in play and the result is that Bill is 30 years old.

Listing 5-1. Using a Dynamic Object

static void Main(string[] args)
{
    RunDynamicObjectAsStaticAndDynamicExample();
    Console.ReadLine();
}
private void RunDynamicObjectAsStaticAndDynamicExample()
{
    dynamic bob = new Customer("Bob", 30);
    Console.WriteLine("Bob is {0} years old.", bob.Age);
    Customer bill = new Customer("Bill", 30);
    Console.WriteLine("Bill is {0} years old.", bill.Age);
}

By itself, the code in Listing 5-1 will not compile. You need the code in Listings 5-2 and 5-3 to complete the example. If you’ve downloaded this chapter’s code and want to run the whole example now, you can open the Visual Studio solution file Chapter5.sln and run the DynamicObjectExamples project in debug mode. The Main method in the Program.cs file of the DynamicObjectExamples project will call the RunDynamicObjectAsStaticAndDynamicExample method in Listing 5-1 and you should see output that looks like this:

Getting member Age
Bob is 3 years old.
Bill is 30 years old.

Listing 5-1 shows the effects of using a dynamic object as is. It also shows the effects of using a dynamic object as a static object. In previous chapters, we saw examples of static objects being treated as is and as dynamic objects. Table 5-1 summarizes all of the four cases using examples.

Table 5-1. Dynamic and Static Objects and Their Uses

Used as Static Object Used as Dynamic Object
Static object String hello = “hello”; dynamic hello = “hello”;
Dynamic object Customer bill = new Customer("Bill", 30); dynamic bob = new Customer("Bob", 30);

The next section will explain the DLR machinery for implementing dynamic objects and show how the Customer class we used in Listing 5-1 is implemented.

IDynamicMetaObjectProvider Interface

A dynamic object is an object that knows how to do its own late binding. Under the hood, a dynamic object does not itself carry the weight of performing late binding. Instead, it has a big brother, a meta-object, that does the heavy lifting. From a software-design perspective, the distinction between dynamic object and meta-object is nice because it decouples the late-binding logic of a dynamic object from the dynamic object itself. With that distinction, a class like Customer can still focus on the logic that’s specific to the application domain without being cluttered with other responsibilities like the late-binding logic. In other words, the code in the Customer class will not be mixed with the code that implements late-binding logic.

The DLR defines an interface called IDynamicMetaObjectProvider. It’s that interface that provides the meta-object. If a class implements the IDynamicMetaObjectProvider interface, instances of the class are dynamic objects. IDynamicMetaObjectProvider defines only the following method:

DynamicMetaObject GetMetaObject(Experssion parameter);

This is the method that returns the “big brother” of a dynamic object. When client code needs a dynamic object to do some sort of late binding, at some point in the whole late-binding process, the call site binder will call the GetMetaObject method on the dynamic object and get back a meta-object. The call site binder will then ask the meta-object to do the real work of late binding. This is the interoperability protocol described in the previous chapter. The return type of the GetMetaObject method is DynamicMetaObject, which is the base class of all meta-objects in the DLR. In the DLR, a meta-object must be an instance of DynamicMetaObject or of a class that directly or indirectly inherits DynamicMetaObject.

As an example, let’s see an implementation of the IDynamicMetaObjectProvider interface. Listing 5-2 shows the code for the Customer class, which implements the IDynamicMetaObjectProvider interface. This effectively makes all instances of the Customer class dynamic objects. The Customer class defines a Name and an Age property. It has a constructor that takes a customer’s name and age as input. The focus here is of course the GetMetaObject method. The method simply creates an instance of ConstantMetaObject and returns it. The class ConstantMetaObject is where all the late-binding logic resides. As you can see, the Customer class only has things like customer name and age that are relevant to its application domain. The binding logic is decoupled and isolated into the ConstantMetaObject class.

Listing 5-2. Customer.cs

public class Customer : IDynamicMetaObjectProvider
{
    public Customer(String name, int age)
    {
        this.Name = name;
        this.Age = age;
    }
    public String Name { get; set; }
    public int Age { get; set; }
    #region IDynamicMetaObjectProvider Members
    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new ConstantMetaObject (parameter, this);
    }
    #endregion
}

ConstantMetaObject is the class that implements the late-binding logic and, obviously, that’s where the meat as well as the complexity is. Let’s see next what ConstantMetaObject looks like.  

Dynamic Meta Objects

To be a class of meta-objects, ConstantMetaObject must inherit directly or indirectly from the DynamicMetaObject class. In Listing 5-3, ConstantMetaObject inherits directly from DynamicMetaObject and overrides the BindGetMember method it inherits from DynamicMetaObject. ConstantMetaObject overrides BindGetMember so it can provide its own late binding logic for the GetMember action, one of the twelve actions DLR defines in its common type system. The custom late-binding logic ConstantMetaObject provides for the GetMember action is to always return the constant number 3. This is an introductory example and the implementation is trivial. We will get to more realistic examples later when we will look at a fluent API that implements the late-binding logic for constructing XML documents.

Listing 5-3 shows the code for ConstantMetaObject. As you can see from the code listing, BindGetMember calls the ReturnConstant helper method. The ReturnConstant method calls the constructor of DynamicMetaObject and passes it two things—a DLR expression that represents the number 3 and a binding restriction that always evaluates to true. There are several important points to note about that code, in particular the use of a DynamicMetaObject instance as the return value of ReturnConstant and hence the return value of BindGetMember. At the beginning of this section, I described DynamicMetaObject as the base class that all classes of meta-objects must inherit from directly or indirectly. Now the code is showing you the use of DynamicMetaObject as the result of late binding. It turns out that DynamicMetaObject has two responsibilities. The first is to serve as the base class of the classes that implement custom late-binding logic of meta-objects. The second is to carry the result of late binding.

In the code example, the BindGetMember method being overridden is evidence of DynamicMetaObject’s first responsibility. The fact that BindGetMember’s return type is DynamicMetaObject is evidence of DynamicMetaObject’s second responsibility. If you recall, in Chapter 3 I said that the overall result of late binding is called a rule and that a rule consists of two parts—the raw late-binding result and the conditions (i.e. restrictions) under which the raw result is valid. That’s exactly why here in the ReturnConstant method you see that when DynamicMetaObject is used to carry the overall result of late binding, the code needs to pass not only the raw late-binding result (i.e., the DLR expression that represents the number 3) but also a restriction to the constructor of DynamicMetaObject.

Listing 5-3. ConstantMetaObject.cs

public class ConstantMetaObject  : DynamicMetaObject
{
    public ConstantMetaObject (Expression expression, object obj)
        : base(expression, BindingRestrictions.Empty, obj)
    { }
    private DynamicMetaObject ReturnConstant()
    {
        //returns constant 3
        return new DynamicMetaObject(
            Expression.Convert(Expression.Constant(3), typeof(object)),
            BindingRestrictions.GetExpressionRestriction(Expression.Constant(true)));
    }

    public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
    {
        Console.WriteLine("Getting member {0}", binder.Name);
        return ReturnConstant();
    }}

Now you’ve seen the three parts that make up the introductory example—the Customer class, the ConstantMetaObject class, and the client code that puts everything together. The next few sections will look at the DynamicMetaObject class and its two responsibilities in more detail.

DynamicMetaObject and Binding Logic

Chapter 4 describes the DLR common type system and its twelve late-binding actions. For each late-binding action, a language implementer needs to implement a binder class that contains the language’s custom late-binding logic for that action. As a result, there are twelve binder classes a language implementer needs to provide if the language is to support all twelve actions. Each binder class deals with only one action. The story on the dynamic object side is a little different. Like binders, a dynamic object’s meta-object also needs to have its custom late-binding logic for the twelve actions. However, unlike a binder class, the class of a meta-object deals with all twelve actions. When we pick a binder to use, we know a priori which late-binding action the binder is for. The examples in the previous chapter demonstrate that. On the other hand, when we pass a dynamic object around, the same dynamic object can be used in different late-binding actions. Therefore, it makes sense for a meta-object to have late-binding logic for all twelve actions.

The class DynamicMetaObject defines twelve virtual methods that correspond to the twelve actions. Figure 5-1 shows the class diagram of DynamicMetaObject. As you can see, the names of the twelve methods all begin with Bind followed by the late binding action the method is for. I will use the notation Bind[Operation] to refer to these methods. Each Bind[Operation] method takes a binder and optionally some instances of DynamicMetaObject as input parameters. The return type of the twelve methods is also DynamicMetaObject. The twelve virtual methods are implemented in DynamicMetaObject to provide the default late-binding behaviors for dynamic objects. The default late-binding behaviors implemented in DynamicMetaObject simply fall back to the binder, the first input parameter of each method, for performing the late binding. Subclasses of DynamicMetaObject are supposed to override the twelve methods and implement their own custom binding logic.

image

Figure 5-1. Class diagram of the DynamicMetaObject class

A language doesn’t need to have binders that support all twelve late-binding actions. Likewise, a meta-object doesn’t need to have late-binding logic for all twelve actions. As you’ll see in the XmlBuilder example later, the subclasses of DynamicMetaObject in that example have late-binding logic only for some of the twelve actions. If a Bind[Operation] method of DynamicMetaObject is not overridden in a subclass, the default behavior of that Bind[Operation] method is to fall back to the host language’s binder and let the binder do the binding, as we saw in the previous chapter. If you recall, during the late-binding process, static objects are wrapped by instances of DynamicMetaObject and the default binding behavior of those wrapper objects is to fall back to the host language’s binders.

DynamicMetaObject and Binding Result

The previous section described the DynamicMetaObject class as the ultimate base class of all classes that implement custom the late-binding logic of meta-objects. Now we will delve into DynamicMetaObject as the carrier of late-binding results.

The class diagram in Figure 5-1 shows six properties in the DynamicMetaObject class. The Expression property is the one that stores the raw binding result. The Restrictions property stores the conditions under which the raw binding result is valid. Together Expression and Restrictions make up the overall binding result. Eventually, those two properties are turned into a rule and the rule is cached in the L0, L1 and L2 caches we looked at in Chapter 3. The place where the two properties are turned into a rule is in the Bind method that DynamicMetaObjectBinder inherits from CallSiteBinder and overrides. Here is a code excerpt that shows the conversion from the two properties to a rule. You can download the DLR source code and find the complete code of the Bind method in DynamicMetaObjectBinder.cs.

public sealed override Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel) {

    //Skip code that does some checking.
    //Skip code that converts args and parameters into instances of DynamicMetaObject.

    DynamicMetaObject binding = Bind(target, metaArgs);

    //Skip code that does some checking.

    Expression body = binding.Expression;
    BindingRestrictions restrictions = binding.Restrictions;

    //Skip code that does some checking and processing of body and restrictions.

    if (restrictions != BindingRestrictions.Empty) {
        body = Expression.IfThen(restrictions.ToExpression(), body);
    }

    return body; //This is the rule.
}

Understanding the overridden Bind method in DynamicMetaObject is very important to a solid understanding of how the DLR works. The code excerpt highlights the key steps the overridden Bind method performs. First, it converts args and parameters into instances of DynamicMetaObject, as detailed in the previous chapter. Next, it calls the abstract Bind method, which subclasses of DynamicMetaObjectBinder, like GetMemberBinder and InvokeMemberBinder, implement. The overall result of the late binding is represented by an instance of DynamicMetaObject, and that instance is assigned to a variable called binding. Then the code takes the Expression and Restrictions properties of the binding variable and assigns them to the variables body and restrictions respectively. After some checking and processing of the body and restrictions variables, the code calls the static Expression.IfThen method to turn body and restrictions into a DLR expression that represents a rule. The call to Expression.IfThen basically creates a new expression that says “if the restrictions are true, then the body is the valid binding result”.

Interoperability

The ConstantMetaObject class so far only provides logic that governs the late binding of the GetMember action. There are eleven other operations whose late-binding behavior we can define. Let’s extend the ConstantMetaObject to cover those actions and then have some fun with it in C# and IronPython. Listing 5-4 shows the code for the extended ConstantMetaObject. As you can see, the class ConstantMetaObject overrides all of the twelve Bind[Operation] methods it inherits from DynamicMetaObject. Except for the BindConvert method, the code in each overridden method simply calls the ReturnConstant helper method you saw you in Listing 5-3. In the interest of saving trees, Listing 5-4 shows only a couple of these methods. BindConvert is a little special because I want it to return the number 3 as an integer, not as an object. The ReturnConstant helper method returns the number 3 as an object. So the BindConvert method does not call the ReturnConstant method like the others do.

Listing 5-4. ConstantMetaObject.cs

public class ConstantMetaObject : DynamicMetaObject
{
    public ConstantMetaObject(Expression expression, object obj)
        : base(expression, BindingRestrictions.Empty, obj)
    { }

    public override DynamicMetaObject BindConvert(ConvertBinder binder)
    {
        Console.WriteLine("BindConvert, binder.Operation: {0}", binder.ReturnType);
        return new DynamicMetaObject(
            Expression.Constant(3, typeof(int)),
            BindingRestrictions.GetExpressionRestriction(Expression.Constant(true)));
    }

    public override DynamicMetaObject BindInvoke(InvokeBinder binder,
        DynamicMetaObject[] args)
    {
        Console.WriteLine("BindInvoke, binder.ReturnType: {0}", binder.ReturnType);
        return ReturnConstant();
    }

    public override DynamicMetaObject BindInvokeMember(
        InvokeMemberBinder binder, DynamicMetaObject[] args)
    {
        Console.WriteLine("BindInvokeMember, binder.ReturnType: {0}", binder.ReturnType);
        return ReturnConstant();
    }

    //Other Bind[Action] methods omitted.
}

Listing 5-5 shows the code that triggers the various late-binding actions on an instance of the Customer class. When a late-binding action is triggered, the corresponding Bind[Action] method in ConstantMetaObject will be invoked to do the late binding. From the code in Listing 5-5, you can see that the code bob.Foo(100) invokes a member method on the dynamic object bob and therefore it triggers the InvokeMember action. Similarly the code bob[100] tries to get the 100th index of the dynamic object bob and hence triggers the GetIndex action.

Listing 5-5. Triggering Late-Binding Actions in C#

private static void RunCustomerLateBindingInCSharpExamples()
{
    dynamic bob = new Customer("Bob", 30);
    Console.WriteLine("bob.Foo(100): {0}", bob.Foo(100));       //InvokeMember
    Console.WriteLine("bob(): {0}", bob());                     //Invoke
    Console.WriteLine("bob[100]: {0}", bob[100]);               //GetIndex
    Console.WriteLine("(bob[100] = 10): {0}", (bob[100] = 10)); //SetIndex
    Console.WriteLine("(int) bob: {0}", (int)bob);              //Convert
    Console.WriteLine("(bob.Age = 40): {0}", (bob.Age = 40));   //SetMember
    Console.WriteLine("bob.Age: {0}", bob.Age);                 //GetMember
    Console.WriteLine("bob + 100: {0}", bob + 100);             //BinaryOperation
    Console.WriteLine("++bob: {0}", ++bob);                     //UnaryOperation
}

C#, of course, is not the only language that has access to the Customer class. We can write Python code similar to the code in Listing 5-5 and expect to see the same late-binding behavior. Listing 5-6 shows the Python code that triggers the various late-binding actions on the variable bob. The variable bob comes from the C# code shown in Listing 5-7. From the code in Listing 5-6, you can see that the code bob() invokes the variable bob as a function and that triggers the Invoke action. As a result, the BindInvoke method in ConstantMetaObject is called to do the late binding. Similarly the code del bob.Age deletes the member property Age from the dynamic object bob and hence triggers the DeleteMember action.          

Listing 5-6. CustomerLateBinding.py

print bob()     #Invoke
print bob[100]  #GetIndex
print bob.Age   #GetMember
print bob + 100 #BinaryOperation
print ++bob     #UnaryOperation
bob[100] = 10   #SetIndex
bob.Age = 40    #SetMember
del bob.Age     #DeleteMember
del bob[100]    #DeleteIndex

If you compare the C# code in Listing 5-5 and the Python code in Listing 5-6, you’ll notice some differences between those two listings. In Listing 5-5, we have the C# code bob.Foo(100). When the C# code runs, the BindInvokeMember method of ConstantMetaObject will be called to perform the late binding for the invocation of the Foo method on the variable bob. However, we can’t write bob.Foo(100) in Listing 5-6’s Python code. If we do that, we’ll get an exception that says “int is not callable” because in Python, the code bob.Foo(100) will result in a late-bound GetMember action for getting the “Foo” member of the variable bob. Whatever is returned by the late-bound GetMember action will then be called like a delegate with 100 as an input argument. In our case, the late-bound GetMember action for getting the “Foo” member of the variable bob will return the number 3. The IronPython runtime will therefore try to invoke the number 3 with 100 as an input argument. Since the number 3 is not callable, we get an exception that says “int is not callable”.  

The C# and Python code also differ in that in the C# code, we can’t trigger late-bound actions such as DeleteMember and DeleteIndex, but in Python code, we can. The reason for that is because the C# language does not have syntax for deleting a member of an object, nor does it have syntax for deleting an index of an object. This seems like a serious problem because a dynamic object can be passed around and used in different languages. The dynamic object might provide late-binding behavior for a late-bound action like DeleteMember but our ability to trigger that late-bound action depends on which language we use. Fortunately there is a solution to this problem. The solution is to use call sites and binders. We will go through the details of the solution in Chapter 12 when we discuss some more advanced topics of binders.

Another difference between Listing 5-5 and Listing 5-6 is that code like bob.Age = 40 in Python is a statement that does not yield a value. Because the code bob.Age = 40 in Python does not yield a value, we can’t write code such as the print bob.Age = 40 in Listing 5-6. On the other hand, the code bob.Age = 40 in C# is an expression that yields a value. (In our case, the value it yields is the number 3.) Because of that, we can have this code Console.WriteLine("(bob.Age = 40): {0}", (bob.Age = 40)); in Listing 5-5 to print out the value.

The last difference I want to point out between Listing 5-5 and Listing 5-6 has to do with the order in which the late-bound actions are triggered. In Listing 5-5, I purposely trigger the ++ unary operation on the variable bob at the end. This is because the ++ unary operation triggered in C# has the effect of assigning the result of the unary operation to the variable bob. Because of that, after the ++ unary operation is bound and executed, the variable bob will have the value 3 and we can no longer trigger other late-binding actions on it. Compare that to the Python code in Listing 5-6 and you will see that the ++ unary operation triggered in Python does not have the effect of assigning the result of the unary operation to the variable bob.

To run the Python code in Listing 5-6, the example uses the RunCustomerLateBindingInPythonExamples method shown in Listing 5-7. The method uses the DLR Hosting API to create a script engine for running Python code. The Python code is run in something called a scope. The scope is an instance of ScriptScope and it is the execution context in which the Python code runs. As an execution context, a scope provides variable name bindings. This is the kind of name binding we talked about in Chapter 2 and is not to be confused with late binding. In this example, as the bolded line shows, the scope binds the variable name “bob” to the dynamic object referenced by the variable bob. Because the variable name is bound in the scope, the Python code in Listing 5-6 can use that variable name directly. That’s as much as I have to say about the DLR Hosting API for now. The DLR Hosting API is a big topic and Chapter 6 will cover it in detail.

Listing 5-7. Triggering Late Binding Actions in IronPython

private static void RunCustomerLateBindingInPythonExamples()
{
    dynamic bob = new Customer("Bob", 30);
    ScriptEngine pyEngine = IronPython.Hosting.Python.CreateEngine();
    ScriptSource source = pyEngine.CreateScriptSourceFromFile("CustomerLateBinding.py");
    ScriptScope scope = pyEngine.CreateScope();
    scope.SetVariable("bob", bob);
    source.Execute(scope);
}

META OBJECT PROTOCOL (MOP)

DynamicObject Class

DynamicObject is essentially a wrapper around the twelve methods in DynamicMetaObject. For each of those twelve methods, there is a corresponding virtual method in DynamicObject. For example, for the BindSetMember method in DynamicMetaObject, there is a method called TrySetMember in DynamicObject. I will use the notation Try[Operation] to refer to the twelve virtual methods in DynamicObject that correspond to the twelve Bind[Operation] methods in DynamicMetaObject. The key to understanding how DynamicObject works is the relation between those two sets of methods. Let’s see how the two sets of methods are related.

You use DynamicObject by deriving a class from it. In the derived class, you override some or all of the twelve Try[Operation] methods defined by the DynamicObject class. In each overridden method, you implement the late-binding logic you want your dynamic object to have. For example, Listing 5-8 shows a class that inherits from DynamicObject. The class is called Employee and it overrides only the TryGetMember method. The overridden TryGetMember method has the custom binding logic for the GetMember action. Like other introductory examples in this book, the binding logic is to return the constant number 3 as the result. So inside the overridden TryGetMember method, the code assigns number 3 to the parameter result and returns true to signal that the binding is successful. The code looks much simpler than the code in ConstantMetaObject. Because ConstantMetaObject inherits from DynamicMetaObject directly, the code there needs to deal with DLR expressions and DynamicMetaObject instances. Here in the TryGetMember method, the code looks like ordinary C# code. Instead of creating a ConstantExpression instance to represent the number 3 as ConstantMetaObject does, the code in Listing 5-8 simply uses the number 3 as is.

Listing 5-8. Employee.cs

class Employee : DynamicObject
{
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = 3;
        return true;
    }

    public int Salary { get { return 500; } }

    public double calculateBonus(int performanceRating)
    {
        return 1000d;
    }
}

Listing 5-9 shows the code that uses the Employee class from Listing 5-8. The Employee class defines a Salary property, but not an Age property. When the code in Listing 5-9 accesses the Salary property of the employee variable, because of the way the late-binding logic of DynamicObject is implemented, the Salary property getter of the Employee class will be called and the number 500 will be returned. But when the code in Listing 5-9 accesses a property like Age that is not defined in the Employee class, the TryGetMember method will be called and the number 3 will be returned.

Listing 5-9. Using the Employee Class

private static void RunDynamicObjectExample()
{
    dynamic employee = new Employee();
    Console.WriteLine("Employee's salary is {0} dollars.", employee.Salary);
    Console.WriteLine("Employee is {0} years old.", employee.Age);
    Console.WriteLine("Employee's bonus is {0} dollars.", employee.calculateBonus(2));
}

If you run the code in Listing 5-9, you’ll see output like the following:

Employee’s salary is 500 dollars.
Employee is 3 years old.
Employee’s bonus is 1000 dollars.

Now let’s dive a little deeper and see how the late-binding logic of DynamicObject is implemented. DynamicObject implements IDynamicMetaObjectProvider. That’s no different from the Customer class we saw in Listing 5-2, and it also means instances of DynamicObject are base objects that have accompanying meta-objects. Whereas the Customer class uses ConstantMetaObject to hold the real late binding logic, DynamicObject uses a private class called MetaDynamic. MetaDynamic inherits from DynamicMetaObject and overrides the Bind[Operation] methods defined in DynamicMetaObject. So far, everything is business as usual. The trick is in the overridden Bind[Operation] methods.

A Bind[Operation] method in MetaDynamic checks if the corresponding Try[Operation] method in DynamicObject is overridden. If not, it falls back to the host language’s binder and let the binder do the binding. If the binder is able to do the binding, that’s fine. If the binder is unable to do the binding, then whatever error the binder throws will surface up to the client code. On the other hand, if the corresponding Try[Operation] method is overridden, the Bind[Operation] method falls back to the host language’s binder to see if the binder is able to do the binding. If the binder is able to do the binding, again that’s fine. The binding result of the binder will be returned. In the code example in Listing 5-9, this is the case when the code tries to get the Salary property of employee. In that case, because the Salary property is defined in the Employee class, the C# language’s binder is able to do the late binding for the code employee.Salary. And since the C# language’s binder can do the binding, the TryGetMember method we override in the Employee class will not be invoked to do the late binding.

The following code shows the actual expression that represents the binding result of this case. The expression will be wrapped by a LambdaExpression object that will be compiled into IL and cached in L0, L1 and L2 caches. Essentially, you can view the expression below as the rule that represents the late-binding result. Here the rule has one restriction—the if condition. The restriction checks whether the dynamic object ($$arg0) is an instance of the Employee class. In our example, $$arg0 refers to the employee variable and hence is an instance of the Employee class. Since the condition is met, the if-true branch in the expression will be executed. This expression is a GotoExpression (the .Return) that contains a BlockExpression (the .Block). The BlockExpression contains a child expression that contains other child expressions and so on. Leaving all those details aside, the part in the BlockExpression that’s of interest to us is the MethodCallExpression (the .Call). It contains a ConstantExpression that holds a MethodInfo object for the get_Salary method. The MethodCallExpression basically represents a call of the Salary property’s get method on the Employee instance referenced by $$arg0.

.If (
    $$arg0 .TypeEqual DynamicObjectExamples.Employee
) {
    .Return #Label1 { .Block() {
        (System.Object)((System.Int32).Call
            .Constant<System.Reflection.MethodInfo>(Int32 get_Salary())
            .Invoke(
               $$arg0,
               .NewArray System.Object[] {}
            ))
  }}
} .Else {
    .Default(System.Void)
}

So far we looked at the case where the host language’s binder was able to do the binding and the Try[Operation] method overridden in Employee was not invoked. Now let’s see what happens when the host language’s binder is unable to do the late binding. If the corresponding Try[Operation] method is overridden, and the host language’s binder is unable to do the binding, the Bind[Operation] method implemented in DynamicObject will produce a rule like the following. The part of interest to us is in bold. As you can see, the bolded part is a ConditionalExpression (.If). The if-condition is a MethodCallExpression that represents a call to the TryGetMember method on the Employee instance referenced by $$arg0. And if the TryGetMember returns true, the if-true part will return $var1. What is $var1? $var1 is the out parameter result of TryGetMember. If you now look back at the code in TryGetMember in Listing 5-8, you will see why the method sets the result parameter to 3 and returns true.

.If (
    $$arg0 .TypeEqual DynamicObjectExamples.Employee
) {
    .Return #Label1 { .Block() {
        .Block(System.Object $var1) {
            .If (
                .Call ((System.Dynamic.DynamicObject)$$arg0).TryGetMember(                    
                     .Constant<System.Dynamic.GetMemberBinder>(...),
                     $var1)
            ) {
                $var1
            } .Else {
                .Block() {
                    .Block() {
                        .Throw .New System.MissingMemberException("Age");
                        .Default(System.Object)
                    }
                }
            }
        }
    } }
} .Else {
    .Default(System.Void)
}

XML Builder

The examples so far in this chapter are somewhat trivial. It’s nice to use simple examples to explain concepts and occasionally look at the binding rules to see how things work under the hood. It’s also important to see some practical examples and not lose sight of the forest for the trees, and that’s the purpose of this section. Our practical example will implement a fluent API for building XML documents. The next few chapters will demonstrate more applications of what you learn in this chapter. You can find all of the code for this part of the chapter in the DynamicBuilder project of this chapter’s code download.

Before I show you how the API is implemented, let’s see first how the API makes it easy to build XML documents. Listing 5-10 shows code that uses the API to build an XML document. The XML document it builds is this:

<Customer>
    <Name FirstName=”John” LastName=”Smith”>John Smith</Name>
    <Phone>555-8765</Phone>
    <Address>
        <Street>123 Main Street</Street>
        <City>Fremont</City>
        <Zip>55555</Zip>
    </Address>
</Customer>

As you can see, the code in the RunXmlBuilderExample method in Listing 5-10 is pretty much the same in structure as the XML file it generates. That’s the selling point of the API. If you’ve used classes like XmlDocument, XmlElement, and friends in the .NET Class Library, you know the fluent API demonstrated in Listing 5-10 offers a more intuitive, domain-specific programming interface to XML document construction. That said, the API shown here is very limited in its capabilities and is for the purpose of illustration only. Also, the idea of the fluent API for building XML documents is not something new. Groovy, a dynamic language that runs on the Java virtual machine, has fluent APIs for building XML as well as other things.

The design of the API uses only three late binding actions—GetMember, Invoke, and InvokeMember. The design uses two special properties—b and d— to mark the beginning and the end of a block of child XML elements. For example, Street, City, and Zip are three child elements of Address because they are within the block marked by the b and d that belong to Address. Some might prefer using curly braces instead of b and d. Although that makes the syntax of the API better, the downside is you can’t use the API in languages that don’t support that syntax. For example, if we replace b and d with curly braces in the example, the C# compiler will report compilation errors. While developing the XML builder example, I tried to strike a balance so that the API can be used in as many languages as possible and at the same time its syntax remains reasonably fluent.

Listing 5-10. Using the Fluent API to Build an XML Document

static void Main(string[] args)
{
    RunXmlBuilderExample();
    Console.ReadLine();
}

private static void RunXmlBuilderExample()
{
    String xml = XmlBuilder.Create()
            .Customer.b
                .Name("FirstName", "John", "LastName", "Smith", "John Smith")
                .Phone("555-8765")
                .Address.b
                    .Street("123 Main Street")
                    .City("Fremont")
                    .Zip("55555")
                .d
            .d
            .Build();

    Console.WriteLine(xml);
}

Because the API uses the GetMember, Invoke, and InvokeMember actions, any language whose syntax supports those three actions can use the API. Listing 5-11 is an example of using the API in Ruby. The Ruby code is pretty self-explanatory, except perhaps for the many occurrences of to_clr_string, which are there because Ruby strings are not the same as normal .NET strings. For one thing, Ruby strings are mutable while normal .NET strings are not. Our fluent XML API expects normal .NET strings and, therefore, in the Ruby code, we use to_clr_string to convert a Ruby string to a normal .NET string. In some versions of IronRuby, the conversion might happen automatically, but I put to_clr_string in the Ruby code just to be safe. To run the Ruby code, I use the same technique as in the code in Listing 5-7. Listing 5-12 shows the C# code that uses an IronRuby script engine to run the Ruby code in Listing 5-11. I’ll skip the explanation of the code in Listing 5-12 since it’s the same as the explanation for the code in listing 5-7.  

Listing 5-11. XmlBuilder.rb

xml = xmlBuilder.
 Customer.b.
     Name("FirstName".to_clr_string, "John".to_clr_string,
        "LastName".to_clr_string, "Smith".to_clr_string, "John Smith".to_clr_string).
     Phone("555-8765".to_clr_string).
     Address.b.
         Street("123 Main Street".to_clr_string).
         City("Fremont".to_clr_string).
         Zip("55555".to_clr_string).
     d.
 d.
 Build()

puts xml

Listing 5-12. Running the Ruby code in XmlBuilder.rb

private static void RunXmlBuilderInRubyExample()
{
    ScriptEngine rbEngine = IronRuby.Ruby.CreateEngine();
    ScriptSource source = rbEngine.CreateScriptSourceFromFile("XmlBuilder.rb");
    ScriptScope scope = rbEngine.CreateScope();
    scope.SetVariable("xmlBuilder", XmlBuilder.Create());
    source.Execute(scope);
}

You can also use the API in Python to build XML documents. Listing 5-13 shows an example. The Python code in Listing 5-13 is very close to the XML it constructs. The one annoying thing in the Python code is the backslash characters. Because white space and new line characters in Python play a role in the language’s syntax, we need the backslash characters to tell the Python parser to treat those multiple lines as one logical line of code.

Listing 5-13. XmlBuilder.py.

xml = xmlBuilder.
  Customer.b.
    Name("FirstName", "John", "LastName", "Smith", "John Smith").
    Phone("555-8765").
    Address.b.
      Street("123 Main Street").
      City("Fremont").
      Zip("55555").
    d.
  d.
  Build()

print xml

To run the Python code in Listing 5-13, you can use the code in Listing 5-14, which uses the DLR Hosting API to run Python code and is very similar to the code in Listing 5-12. Chapter 6 will cover the DLR Hosting API in detail.

Listing 5-14. Running the Python Code in XmlBuilder.py.

private static void RunXmlBuilderInPythonExample()
{
    ScriptEngine engine = IronPython.Hosting.Python.CreateEngine();
    ScriptSource source = engine.CreateScriptSourceFromFile("XmlBuilder.py");
    ScriptScope scope = engine.CreateScope();
    scope.SetVariable("xmlBuilder", XmlBuilder.Create());
    source.Execute(scope);
}

The implementation of the XML builder API consists of four classes—XmlBuilder, NodeBuilder, ChildNodesBuilder, and XmlBuilderHelper. Most of the real work is done in NodeBuilder and ChildNodesBuilder. The class NodeBuilder corresponds to an XML element. The class ChildNodesBuilder corresponds to a block of child elements. Listing 5-15 shows the code for NodeBuilder. An XML element has a tag name, and optionally a body, some attributes, or some child elements. Therefore, the NodeBuilder class defines the private member variables—name, body, attributes and childNodes—to represent the various parts of an XML element. Notice that the type of the childNodes member variable is ChildNodesBuilder. That’s because the API implementation uses ChildNodesBuilder as a container for a list of child XML elements. Each child XML element in ChildNodesBuilder is an instance of NodeBuilder. When a ChildNodesBuilder object contains a NodeBuilder object as a child XML element, the ChildNodesBuilder object is set as the parent of the NodeBuilder object.

NodeBuilder inherits from DynamicObject. It overrides TryGetMember, TryInvoke, and TryInvokeMember. The code Phone("555-8765").Address will cause the TryGetMember method to be called because it means getting the Address member property of the NodeBuilder object that represents the Phone("555-8765") XML element. The C# code Street("123 Main Street").City("Fremont") will cause the TryInvokeMember method to be called because it means calling the City method with the string “Fremont” as the argument on the NodeBuilder object that represents the Street("123 Main Street") element.

The Python code .Phone("555-8765") in XmlBuilder.py will cause the TryInvoke method of NodeBuilder to be called. What happens is the code .Phone("555-8765") is invoked on an instance of ChildNodesBuilder. As a result, the TryGetMember method of ChildNodesBuilder will be called and the TryGetMember method will return the NodeBuilder instance that represents the Phone XML element. The NodeBuilder instance that represents the Phone XML element will be treated as a callable object by Python. Python will call the NodeBuilder instance because the NodeBuilder instance is a delegate and the TryInvoke method of NodeBuilder will be invoked to handle the late binding.

Besides overriding TryGetMember, TryInvoke, and TryInvokeMember, NodeBuilder defines two property getters—b and d. Because of the way the late-binding logic of DynamicObject is implemented, code like Address.b will cause the property getter b, not TryGetMember, to be called.

Listing 5-15. NodeBuilder.cs

public class NodeBuilder : DynamicObject
{
    private string name;  //tag name of the Xml element this node builder represents
    private string body;
    private ChildNodesBuilder childNodes;
    private IDictionary<object, object> attributes;
    private ChildNodesBuilder parent;

    internal NodeBuilder(ChildNodesBuilder parentNode, string name, string body = null,
        IDictionary<object, object> attributes = null)
    {
        this.parent = parentNode;
        this.name = name;
        this.body = body;
        this.attributes = attributes;
    }

    public ChildNodesBuilder b
    {
        get
        {
            this.childNodes = new ChildNodesBuilder(parent);
            return childNodes;
        }
    }

    public ChildNodesBuilder d
    {
        get { return parent.Parent; }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        NodeBuilder newNode = new NodeBuilder(parent, binder.Name);
        parent.addChild(newNode);
        result = newNode;
        return true;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder,
        object[] args, out object result)
    {
        NodeBuilder newNode = XmlBuilderHelper.CreateNodeBuilder(parent, binder.Name, args);
        parent.addChild(newNode);
        result = newNode;
        return true;
    }

    public override bool TryInvoke(InvokeBinder binder, object[] args,
        out object result)
    {
        XmlBodyAttributes bodyAttributes = XmlBuilderHelper.ParseArgs(args);
        this.body = bodyAttributes.TagBody;
        this.attributes = bodyAttributes.Attributes;
        result = this;
        return true;
    }

    internal void Build(StringBuilder stringBuilder)
    {
        stringBuilder.AppendLine();
        stringBuilder.Append("<" + this.name);
        if (this.attributes != null)
        {
            foreach (var keyValuePair in attributes)
                stringBuilder.AppendFormat(" {0}={1}",
                        keyValuePair.Key, keyValuePair.Value);
        }

        if (body != null)
        {
            stringBuilder.AppendFormat(">{0}</{1}>", body, this.name);
            return;
        }

        if (childNodes == null)
        {

            stringBuilder.Append(" />");
            return;
        }
        stringBuilder.Append(">");
        childNodes.Build(stringBuilder);
        stringBuilder.AppendLine();
        stringBuilder.AppendLine("</" + this.name + ">");
    }
}

Listing 5-16 shows the code for ChildNodesBuilder, which inherits from DynamicObject. It overrides TryInvokeMember and TryGetMember. C# code like b.Street("123 Main Street") will cause the TryInvokeMember method of ChildNodesBuilder to be called. That is because the property getter b of NodeBuilder returns an instance of ChildNodesBuilder. After the property getter b of NodeBuilder returns a ChildNodesBuilder instance, the C# code b.Street("123 Main Street") calls the Street method with the string “123 Main Street” as the argument on that ChildNodesBuilder instance. Because the Street method is not defined in the ChildNodesBuilder class, the TryInvokeMember method of ChildNodesBuilder is called to handle the late binding. As for the TryGetMember of ChildNodesBuilder, it will be called, for example, when we execute Ruby code such as .Address in the XmlBuilder.rb file shown in Listing 5-11. This is because.Address is called on an instance of ChildNodesBuilder. Because the ChildNodesBuilder class does not define a property called Address, the TryGetMember method of ChildNodesBuilder is invoked to do the late binding. Let’s look at the implementation of the TryGetMember and TryInvokeMember methods in Listing 5-16.

Every time TryGetMember is invoked, it means we have a new XML node to create. For example, the Ruby code .Address means we need to create a new XML node whose name is “Address”. Once the new XML node is created, we need to add it to the parent XML node that contains it. As mentioned earlier, in our API design, every parent XML node uses an instance of ChildNodesBuilder to contain its child XML nodes. That’s why you see that in the TryGetMember method in Listing 5-16, after creating a new instance of NodeBuilder that represents the new XML node, we add the new NodeBuilder instance as a child of the current ChildNodesBuilder instance.

Listing 5-16. ChildNodesBuilder.cs

public class ChildNodesBuilder : DynamicObject
{
    private List<NodeBuilder> childNodes = new List<NodeBuilder>();
    private ChildNodesBuilder parent;

    internal ChildNodesBuilder(ChildNodesBuilder parent)
    {
        this.parent = parent;
    }

    public ChildNodesBuilder d
    {
        get { return parent; }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        NodeBuilder newNode = new NodeBuilder(this, binder.Name);
        this.addChild(newNode);
        result = newNode;
        return true;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder,
       object[] args, out object result)
    {
        NodeBuilder newNode = XmlBuilderHelper.CreateNodeBuilder(this, binder.Name, args);
        this.addChild(newNode);
        result = newNode;
        return true;
    }

    public String Build()
    {
        StringBuilder stringBuilder = new StringBuilder();
        Build(stringBuilder);
        return stringBuilder.ToString();
    }

    internal ChildNodesBuilder Parent
    {
        get { return parent; }
    }

    internal void addChild(NodeBuilder nodeBuilder)
    {
        childNodes.Add(nodeBuilder);
    }

    internal void Build(StringBuilder stringBuilder)
    {
        foreach (var item in childNodes)
            item.Build(stringBuilder);
    }
}

The implementation of the TryInvokeMember method in Listing 5-16 is very similar to that of TryGetMember. When the TryInvokeMember method is invoked, it means we have a new XML node to create. For example, the C# code .Street(“123 Main Street”) means we need to create a new XML node whose name is “Street” and whose body is “123 Main Street”. The difference between TryGetMember and TryInvokeMember is that TryInvokeMember might take input arguments that represent the attributes and body of the new XML node. In order to handle the input arguments that represent the attributes and body of an XML node, I wrote a small helper class called XmlBuilderHelper and used it in the TryInvokeMember method of ChildNodesBuilder. Listing 5-17 shows the code of the XmlBuilderHelper class.

The XmlBuilderHelper provides a helper method called ParseArgs for parsing the arguments that represent an XML node’s attributes and body. The way XmlBuilderHelper parses arguments is to see first if the total number of arguments is an even number. If there is an even number of arguments, the XML node does not have a body and all of the arguments are attributes. The arguments are grouped into name-value pairs. The first argument is the name of the first attribute, the second argument the value of the first attribute, the third argument the name of the second attribute, the fourth argument the value of the second attribute, and so on. For example, the C# code .Name("FirstName", "John", "LastName", "Smith") has an even number of arguments. The first argument is “FirstName” and that will become the name of the first attribute of the XML node we create. The second argument is “John” and that will become the value of the first attribute of the XML node we create. The XML node created by the C# code .Name("FirstName", "John", "LastName", "Smith") will hence be <Name FirstName=”John” LastName=”Smith” />.

If there are an odd number of arguments passed to the ParseArgs method of XmlBuilderHelper, the last argument will become the body and the rest of the arguments will become the attributes of the XML node being created. So for example, the C# code .Name("FirstName", "John", "LastName", "Smith", “John Smith”) has an odd number of arguments. The XML node created by that C# code will hence be <Name FirstName=”John” LastName=”Smith”>John Smith</Name>.

Listing 5-17. XmlBuilderHelper.cs

internal static class XmlBuilderHelper
{
    public static NodeBuilder CreateNodeBuilder(ChildNodesBuilder parent, string name,
        object[] args)
    {
        XmlBodyAttributes bodyAttributes = ParseArgs(args);
        return new NodeBuilder(parent, name, bodyAttributes.TagBody,
                bodyAttributes.Attributes);
    }

    public static XmlBodyAttributes ParseArgs(object[] args)
    {
        String newTagBody = null;
        int attrLength = args.Length;
        if ((args.Length % 2) == 1) //the element has only body
        {
            newTagBody = args[args.Length - 1].ToString();
            --attrLength;
        }

        Dictionary<object, object> attributes = (attrLength > 0) ?
                new Dictionary<object, object>() : null;
        
        for (int i = 0; i < attrLength; i++)
            attributes.Add(args[i], args[++i]);

        return new XmlBodyAttributes(newTagBody, attributes);
    }
}

internal class XmlBodyAttributes
{
    public String TagBody;
    public Dictionary<object, object> Attributes;

    public XmlBodyAttributes(String tagBody, Dictionary<object, object> attributes)
    {
        this.TagBody = tagBody;
        this.Attributes = attributes;
    }
}

Summary

This chapter focused on the IDynamicMetaObjectProvider interface and the DynamicMetaObject class. If a class implements the IDynamicMetaObjectProvider interface, then instances of the class will have associated meta-objects. Those meta-objects will be instances of DynamicMetaObject or its derivatives. If those meta-objects are instances of a class that derives from DynamicMetaObject, they can have late binding behaviors that are different from the default late-binding behaviors implemented in DynamicMetaObject. We discussed what the default late-binding behaviors implemented in DynamicMetaObject are, and showed how to customize those default late-binding behaviors by implementing the ConstantMetaObject class. We then looked at how to trigger the various late-binding actions on instances of the ConstantMetaObject class.

Next we looked at the DynamicObject class of the DLR and explored its late-binding behaviors. Based on that understanding, we saw how to use the DynamicObject class to build a fluent API for constructing XML documents. There are many interesting ways you can use what you learned in this chapter. For example, in Chapter 8, you’ll see how to leverage the DynamicObject class to implement a metaprogramming component that lets you add or remove methods and properties to or from classes or class instances at run time in C#. A static language like C# does not typically allow adding and removing methods and properties to or from classes or class instances at run time. But as you will see in Chapter 8, with DLR, that becomes possible.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.187.113