C H A P T E R  8

images

Metaprogramming

Metaprogramming is everywhere in the DLR. All the LINQ Expression examples you saw in Chapter 2 and all the DLR Hosting API examples you saw in Chapter 6 are metaprograms. Because metaprogramming plays such a pervasive role in the DLR, we're going to dive deeper into the subject and show you some advanced and marvelous uses of metaprogramming.

We will begin with an overview of metaprogramming. The overview will discuss what metaprogramming is and where it is used in the DLR, then we'll look at one type of metaprogramming that adds and removes methods or properties to or from a class or an instance of a class. We will illustrate the metaprogramming technique in Ruby and Python, then write some infrastructure code that enables us to use the same kind of metaprogramming technique in C# through the DLR. The infrastructure code will consist of two classes called ClassMetaObject and ExpandoClass.

We will then take a detour and build a custom LINQ query provider. The purpose of this exercise is to illustrate a typical use of DLR Expression as a metaprogramming technique. The exciting thing about the custom LINQ query provider is that we are going to gradually evolve it into a code-generation framework that utilizes the ClassMetaObject and ExpandoClass classes. At the end, we will arrive at a code-generation framework that is in spirit similar to frameworks such as the popular Ruby on Rails.

Overview of Metaprogramming

Metaprograms are programs that generate or manipulate other programs, and metaprogramming is, of course, the writing of metaprograms. Based on these definitions, you can see why the LINQ Expression examples in Chapter 2 are metaprograms. They are metaprograms because, in those examples, we construct LINQ expression trees and use those trees to generate executable IL code. The code we write to construct and compile LINQ expression trees is a metaprogram. The programs manipulated by the metaprograms consist of code represented by the LINQ expression tree. Similarly, the DLR Hosting API examples in Chapter 6 are metaprograms because those examples take Python code or Ruby code as input and manipulate the code by running it and interacting with it. The DLR Hosting API examples are metaprograms and the Python code and Ruby code are the programs manipulated by the metaprograms.

It's common to see the concept of code as data in metaprograms. In the case of the LINQ Expression examples in Chapter 2, the LINQ expression trees are data that represent code elements, such as if conditions and while loops. Once code is in the form of data (i.e., expression trees), we can write metaprograms that manipulate the code as data. In the case of the DLR Hosting API examples, the Python and Ruby code is data. Once that code is in the form of data, we can write metaprograms that manipulate the code via the DLR Hosting API.

Metaprograms can generate or manipulate programs at compile time or at runtime. An example of a metaprogram that manipulates a program at compile time is code generation. If you are a C++ programmer and you use C++ macros in your code, you are doing compile-time metaprogramming. That's because, when you compile the C++ code, the macros are processed by the preprocessor to generate code that then gets compiled together with the rest of your code by the C++ compiler. The preprocessor is the metaprogram in this case and the C++ macros in your code are the programs being manipulated by the metaprogram.

Another example of compile-time metaprogramming is a compiler. A compiler is a metaprogram that manipulates the code it compiles. All of the metaprogramming we do with the DLR happens at runtime. For example, when we use the DLR Hosting API to run Python or Ruby code, we do that at runtime. When we use an expression visitor to modify an expression tree or when we compile a lambda expression into an invocable delegate, we do that at runtime. However, that's not to say that we can't do compile-time metaprogramming with the DLR. If we use an expression visitor to modify an expression tree and use that modified tree to generate some C# code at compile time, that would be compile-time metaprogramming.

One common type of metaprogramming is the adding and removing of methods or properties to and from a class at runtime. In static languages like C#, this is in general not supported because classes are compiled at compile time and can't be modified when a program runs.

There are ways to create the illusion that a compiled C# class is modified at runtime. For example, Spring.NET AOP (Aspect Oriented Programming), a library for doing aspect-oriented programming on the .NET platform, generates proxy classes of compiled C# classes at runtime. The proxy classes add aspect-related behavior to the original C# classes and create the illusion that the original C# classes are modified to behave differently. The truth is the original C# classes are not still kept intact. They are just proxied.

images Note: For the sake of thoroughness, I'll point out that there is, in fact, a library that can really modify a compiled Java class as the class is being loaded by a class loader. However, that library is an exceptional case that does not invalidate my main line of discussion in this chapter.

Even though you might be able to modify a compiled Java or C# class at runtime if you are determined, doing so is generally not supported. On the other hand, many dynamic languages naturally support the addition and removal of methods and properties to and from a class at runtime. Moreover, we can also add or remove methods and properties to and from a particular instance of a class without affecting the other instances of the same class. The next part of this chapter will show you how to modify a class and its instances by adding methods or properties at runtime in Ruby, in Python, and, last but not least, in C# through the DLR.

Changing Class Definitions

Now we are going to write some metaprograms that manipulate programs by adding methods or properties to a class or to an instance of a class. We will do the same exercise three times for three different languages—first Ruby, then Python, and finally C# through DLR. Even though C# does not in general support the kind of metaprogramming we discuss in this section, you will see that with the DLR, C# as well as VB.NET developers can benefit the metaprogramming techniques that Ruby and Python developers enjoy. The metaprogramming technique we discuss in this section is the foundation that enables many marvelous applications. As you'll see later in this chapter, we will take what we build in this section to add methods and properties to a C# class or its instances and use it to facilitate runtime code generation.

Ruby

Now we'll see how to define a class in Ruby and then dynamically modify the class. The examples will show you how to add methods to a class so that all instances of the class support those methods. You can also add methods only to a specific instance. In that case, only that specific instance will support those methods and all other instances of the same class will not.

You can try out this section's Ruby code by typing it in an interactive Ruby console. Alternatively, since I have put all the code for this section in the metaExamples.rb file in the MetaExamples project of this chapter's code download, you can simply run the MetaExamples project in debug mode. The MetaExamples project is a .NET console application. The entry point Main method of the console application calls the RunRubyMetaExamples method shown in Listing 8-1 to run the Ruby code in metaExamples.rb. The RunRubyMetaExamples method uses the DLR Hosting API to run the Ruby code. We discussed the DLR Hosting API in detail in Chapter 6, so I won't duplicate that discussion here and explain the code in Listing 8-1.

Listing 8-1. C# Method That Runs the Ruby Code in metaExamples.rb

private static void RunRubyMetaExamples()
{
    ScriptEngine engine = IronRuby.Ruby.CreateEngine();
    engine.ExecuteFile(@"RubymetaExamples.rb");
}

To begin, let's define a Ruby class called Customer as in Listing 8-2.

Listing 8-2. Define a Ruby Class Called Customer

class Customer

  def initialize(name, age)
    @name = name
    @age = age
  end

  def to_s()
    @name
  end
end

The Customer class defines two methods: initialize and to_s. The initialize method will be called by the Ruby runtime when an instance of the Customer class is created. The initialize method takes two input parameters and stores them in two class member variables, @name and @age. In Ruby, the naming convention requires the name of a class member variable begin with @. The to_s method of the Customer class will be called when a Customer instance is converted to a string object, for example, when we print a Customer instance to the console.

With the Customer class defined, let's create a couple of instances of it and see how they work. Listing 8-3 shows this part of the example's code.

Listing 8-3. Create Instances of the Customer Class

bob = Customer.new("Bob", 26)
mary = Customer.new("Mary", 30)
puts bob
puts mary

In Listing 8-3, we create two instances of Customer. In Ruby, a class is an object. The name of a class is a constant that points to the class object. To create an instance of a Ruby class, you call the new method on the name of the class, as the code Customer.new(“Bob”, 26) in Listing 8-3 shows. Because the name of the class is a reference to the class object, what you are effectively doing is calling the new method on the class object, which causes the initialize method of that class to be called. After two instances of Customer are created, we print those two instances to the screen by calling the puts method. The method puts is Ruby's built-in method for printing objects to the screen. Internally, puts calls the to_s method of the Customer class to print out the names of Bob and Mary, our two Customer instances.

To demonstrate the metaprogramming capability Ruby provides, let's suppose we want to modify the Customer class so that we can set Bob and Mary as each other's spouse. To achieve this, we simply use line 2 to add a spouse attribute accessor to the Customer class. The code in line 2 will add two accessor methods, spouse and spouse=, to the Customer class. A class member variable in a Ruby class is by default private and not accessible to the world outside of the class. In order to make the @spouse class member variable accessible to the world outside of the Customer class, we need the code in line 2 that adds the spouse and spouse= accessor methods to the Customer class. With those accessor methods, we can set Mary as Bob's spouse, as line 12 shows. When line 12 assigns the variable mary to the spouse attribute of bob, the spouse= method of bob will be invoked. The spouse= method in Listing 8-4 is implemented in such a way that if Mary is Bob's spouse, then Bob is also set as Mary's spouse. Notice that the code in Listing 8-4 is executed after Listings 8-3 and 8-2. That means even after instances of the Customer class are created in Listing 8-3, we can still modify the class and the instances will just pick up the new spouse and spouse= accessor methods we added. Because the spouse and spouse= accessor methods are added to the Customer class, all the instances of the Customer class will support those two accessor methods.

Listing 8-4. Modify the Customer Class in Ruby

1)   class Customer
2)     attr_accessor :spouse
3)
4)     def spouse=(spouse)
5)       if @spouse != spouse
6)        @spouse = spouse
7)        spouse.spouse = self
8)       end
9)     end
10)  end
11)
12)  bob.spouse = mary
13)
14)  puts "Bob's spouse is " + bob.spouse.to_s
15)  puts "Mary's spouse is " + mary.spouse.to_s

Listing 8-4 shows how to add methods to a class so that all instances of the class will support those methods. Listing 8-5 shows how to add a method to a particular Customer instance, not to the Customer class. The code in adds a calculate_late_fee method to customer Bob and a different calculate_late_fee method to customer Mary. Bob's late fee is 200 while Mary's is 100. Because the calculate_late_fee method is associated with a particular instance, we can have one implementation of the method for Bob that returns 200 and another for Mary that returns 100.

Listing 8-5. Add Methods at the Instance Level

def bob.calculate_late_fee()
    200
end
def mary.calculate_late_fee()
    100
end

puts "Bob's late fee is " + bob.calculate_late_fee.to_s
puts "Mary's late fee is " + mary.calculate_late_fee.to_s

Python

We just saw how to add methods to a class as well as to an instance of a class in Ruby. Now we'll look at the same example in the Python language. You can run this section's Python code listings in sequence by typing them in a Python interactive console. Alternatively, because I have put all the Python code for this section in the metaExamples.py file in the MetaExamples project of this chapter's code download, you can run the MetaExamples project's entry point Main method. This calls the RunPythonMetaExamples method in MetaExamples' Program.cs file to execute the Python code in metaExamples.py.

As in the previous example, our first step is to define the Customer class in Python, as Listing 8-6 shows. The code in Listing 8-6 defines the Customer class as a subclass of the object class. The Customer class contains two methods: __init__ and __str__. The method __init__ is the constructor that will be called when new instances of the Customer class are created. The method __str__ will be called when we convert a Customer instance to a string representation. Methods of a class must take an explicit self argument that represents the instance on which the methods are invoked. That's why both __init__ and __str__ have self as an input parameter. The input parameter does not have to be named self. This is just a naming convention that most Python programmers follow. You can think of the self parameter as sort of Python's equivalent of the this variable in C#. The body of the __init__ method assigns the name and age of a customer to the name and age attributes of the self parameter. Attributes in a Python class like the ones in our example are like class member variables in a C# class. But unlike class member variables in C#, attributes in a Python class don't need to be explicitly declared. That's why you don't see the name and age attributes declared anywhere in Listing 8-6, yet the __init__ method can assign the name and age of a customer to the name and age attributes of the self parameter.

Listing 8-6. Define the Customer Class in Python

class Customer(object):
  def __init__(self, name, age):
    self.name = name
    self.age = age

  def __str__(self):
    return self.name

If we now create instances of the Python Customer class, as Listing 8-7 shows, we can print the string representations of those instances and expect to see the names Bob and Mary show up on the screen.

Listing 8-7. Create Instances of the Customer Class in Python

bob = Customer("Bob", 26)
mary = Customer("Mary", 30)

print bob
print mary

Now here's the part where we add the method for setting a customer's spouse to the Customer class. Listing 8-8 shows how to do that in Python. To add a method to an already defined class in Python, we first define a Python function by itself. In Listing 8-8, we define the set_spouse function alone in lines 1 to 3. The body of the function ensures that if Bob is Mary's spouse, then Mary is also Bob's spouse. After the set_spouse function is defined, we add it to the Customer class as the set_spouse method of the Customer class in line 5. Because the set_spouse method is added to the Customer class, all instances of the Customer class will support that method. In line 7, we test our modification to the Customer class by calling the set_spouse method on the variable bob. When we do this, the set_spouse function is called with the self parameter set to the variable bob. As a matter fact, we can replace line 7 in Listing 8-8 with this equivalent code set_spouse(bob, mary) and everything will work the same. Lines 9 and 10 print out Bob's spouse and Mary's spouse to show that the testing we do in line 7 works correctly.

Listing 8-8. Modify the Customer Class in Python

1)   def set_spouse(self, spouse):
2)     self.spouse = spouse
3)     spouse.spouse = self
4)
5)   Customer.set_spouse = set_spouse
6)
7)   bob.set_spouse(mary)
8)
9)   print "Bob's spouse is " + str(bob.spouse)
10)  print "Mary's spouse is " + str(mary.spouse)

Next we will add methods to individual Customer instances. To do so in Python, we make use of a standard Python module called types. Before we can use that module, we need to do a few things so that the IronPython runtime will be able to locate the module. First, the types module is not included as part of the DLR source code. In order to run the code in Listing 8-9, you need to download IronPython from http://ironpython.codeplex.com and install it. I downloaded IronPython 2.6.1 for .NET 4.0 and installed it in C:Program Files (x86)IronPython 2.6 for .NET 4.0. If you install it in a different folder, you'll need to modify the path in line 2 of Listing 8-9 accordingly. The installation of IronPython places the standard Python types module in C:Program Files (x86)IronPython 2.6 for .NET 4.0Lib. That's the path we need to add to Python's system path so that the IronPython runtime knows to look there for modules we want to import. So in line 2 of Listing 8-9, we add the path to Python's system path. In line 3 we import the types module. Without installing IronPython and without the code in line 2, importing the types module in line 3 would fail.

After the types module is successfully imported, the rest of the code is pretty similar to the Ruby example we saw earlier. We first define a function called bob_late_fee in lines 5 and 6. Then we call the MethodType function of the types module to associate the bob_late_fee function with the variable bob. Similarly for the variable mary, we define a function called mary_late_fee and use the MethodType function of the types module to associate the mary_late_fee function with the variable mary. If you run the code in Listing 8-9, you should see on the screen that Bob's late fee is 200 and Mary's late fee is 100. This shows that in Python, as in Ruby, we can add a method to a particular instance without affecting other instances of the same class.

Listing 8-9. Add Methods to Instances of the Customer Class in Python

1)   import sys
2)   sys.path.append(r'C:Program Files (x86)IronPython 2.6 for .NET 4.0Lib')
3)   import types
4)
5)   def bob_late_fee(self):
6)     return 200
7)
8)   bob.calculate_late_fee = types.MethodType(bob_late_fee, bob)
9)
10)  def mary_late_fee(self):
11)    return 100
12)
13)  mary.calculate_late_fee = types.MethodType(mary_late_fee, mary)
14)
15)  print "Bob's late fee is " + str(bob.calculate_late_fee())
16)  print "Mary's late fee is " + str(mary.calculate_late_fee())

DLR

So far you've seen how to add methods to a class as well as to an instance of a class in both Ruby and Python. Many other dynamic languages, such as Groovy, also provide the means for modifying a class's or an object's behavior at runtime. With the advent of the DLR, the good news is we don't have to code in a dynamic language like Ruby or Python to benefit from the metaprogramming capabilities those languages provide. With a little bit of work, we can define a class in C# and be able to add methods to the class and also to instances of the class. Let's see how that is done.

In this section, we will define two main classes: ClassMetaObject and ExpandoClass. The purpose of ClassMetaObject is to hold the methods we add to individual objects, and the purpose of ExpandoClass is to hold the methods we add to a class. As an example of how you can use these methods, we will define a Customer class that derives from ClassMetaObject. When we create an instance of the Customer class and add methods to that instance, those methods will be stored in an instance of ClassMetaObject. When we add methods to the Customer class, those methods will be stored in an instance of ExpandoClass.

The Customer class code is shown in Listing 8-10. As you can see, the C# Customer class mimics the Ruby and Python Customer classes we saw in the previous sections. The C# Customer class defines a constructor that takes the name and age of a customer. The ToString method is overridden to return the name of a customer. One important thing to note about the C# Customer class is that it derives from ClassMetaObject so that instances of the Customer class can be associated with new properties and methods. Furthermore, the Customer class contains a private static member variable _class that points to an instance of ExpandoClass. This is so that new properties and methods can be added to the Customer class. I made the member variable _class a static variable because I want all instances of the Customer class to share one single ExpandoClass instance that holds all the properties and methods we add to the Customer class. We will look at how ClassMetaObject and ExpandoClass are implemented in a minute. First, however, I'd like to repeat the Ruby and Python examples we saw in the previous sections and show how the same example works in C# using the Customer class.

Listing 8-10. Define the Customer Class in C#

public class Customer : ClassMetaObject
{
    private static ExpandoClass _class = new ExpandoClass();
    public static dynamic CLASS
    {
        get { return _class; }
    }

    private string name;
    private int age;

    public Customer(string name, int age)
    {
        this.name = name;
        this.age = age;
    }

    public override string ToString()
    {
        return this.name;
    }

    protected override ExpandoClass Class
    {
        get { return _class; }
    }
}

Like the Ruby and Python examples in the previous sections, the code in Listing 8-11 creates two instances of the Customer class. After the two Customer instances are created, we want to define a method for setting a customer's spouse and a method for retrieving a customer's spouse. The method we define for setting a customer's spouse is the SetSpouse delegate in Listing 8-11. The delegate takes two input parameters that represent the two parties in a marital relationship. The method body of the SetSpouse delegate enforces the rule that if Bob is Mary's spouse, then Mary must also be Bob's spouse. Notice that the two input parameters of SetSpouse are of the type dynamic. Within the method body of the SetSpouse delegate, the code accesses the Spouse property of the input parameter self. The Spouse property is not defined originally in the C# Customer class. But that's okay because we add the Spouse property to the Customer class in line 16. In line 17, we add the SetSpouse delegate as the SetSpouse method to the Customer class. As you can see from lines 16 and 17, to add a property or method to the Customer class, we add it to the customerClass variable, which is obtained from the static CLASS property of the Customer class. Recall from the code in Listing 8-10 that the static CLASS property of the Customer class returns the single ExpandoClass instance that's shared among all Customer instances and that's meant to store all new properties and methods added to the Customer class.

After adding the Spouse property and the SetSpouse method to the Customer class, we call the SetSpouse method on the variable bob in line 18, just as we did in the previous Ruby and Python examples. When we print out Bob's spouse and Mary's spouse in lines 20 and 21, we can verify that things do work as expected and that we have dutifully officiated at the matrimony. And just as what happens to newlyweds who simply go on a honeymoon and forget to pay their wedding expenses on time, Bob and Mary incurred late fees on their credit card accounts. To reflect that irresponsibility on the part of Bob and Mary, the code in lines 23 and 24 assigns one anonymous delegate as the CalculateLateFee method to the variable bob and another anonymous delegate as the CalculateLateFee method to the variable mary. When you print out Bob's and Mary's late fees, sure enough you will see that Bob has a late fee of 200 and Mary has a late fee of 100 dollars.

Listing 8-11. An Example of Adding Methods at the Class and Instance Levels in C#

1)   private static void RunMetaLibExample()
2)   {
3)       dynamic customerClass = Customer.CLASS;
4)       dynamic bob = new Customer("Bob", 26);
5)       dynamic mary = new Customer("Mary", 30);
6)
7)       Action<dynamic, dynamic> SetSpouse = (self, spouse) =>
8)       {
9)           if (self.Spouse != spouse)
10)          {
11)              self.Spouse = spouse;
12)              spouse.Spouse = self;
13)          }
14)      };
15)
16)      customerClass.Spouse = null;
17)      customerClass.SetSpouse = SetSpouse;
18)      bob.SetSpouse(bob, mary);
19)
20)      Console.WriteLine("Bob's spouse is {0}.", bob.Spouse);
21)      Console.WriteLine("Mary's spouse is {0}.", mary.Spouse);
22)
23)      bob.CalculateLateFee = (Func<int>) (() => { return 200; });
24)      mary.CalculateLateFee = (Func<int>)(() => { return 100; });
25)
26)      Console.WriteLine("Bob's late fee is {0}.", bob.CalculateLateFee());
27)      Console.WriteLine("Mary's late fee is {0}.", mary.CalculateLateFee());
28)  }

Let's see how ClassMetaObject and ExpandoClass are implemented. Listing 8-12 shows the ClassMetaObject code. ClassMetaObject derives from the System.Dynamic.DynamicObject class that the DLR provides. We discussed DynamicObject in Chapter 5.

Basically, the class DynamicObject defines some methods that you can override in a derived class to define the late-binding behavior of the derived class's instances. Here in ClassMetaObject we override the TryGetMember and TrySetMember methods inherited from DynamicObject. The TryGetMember method of ClassMetaObject will be called when we try to access a property or call a method on an instance of ClassMetaObject or a class that derives from ClassMetaObject.

For example, in Listing 8-11, when the code bob.Spouse is executed, because the Spouse property is not defined in the C# Customer class, the TryGetMember method that the Customer class inherits from ClassMetaObject will be called to perform the late binding of the Spouse property. The logic of the TryGetMember method implemented in ClassMetaObject first checks if the requested property or method is available at the instance level by looking up the property or method name in the items dictionary. The items dictionary is a private member variable in ClassMetaObject that holds dynamic properties and methods at the instance level. If the requested property or method is not found at the instance level, the TryGetMember method in ClassMetaObject proceeds to perform the class-level lookup by calling the TryGetMember method on the Class property.

As Listing 8-12 shows, the Class property is a reference to an ExpandoClass instance. The job of the Class property is to hold the dynamic properties and methods at the class level. Because every subclass of ClassMetaObject will have its own class-level dynamic properties and methods, I make the Class property an abstract property. Every subclass of ClassMetaObject should implement the abstract Class property by returning its own ExpandoClass instance in the Class property's get method. That's what the Customer class does, and you can see that if you take a look at how the Class property is implemented in the Customer class in Listing 8-10.

The TrySetMember in ClassMetaObject is implemented a little differently from the TryGetMember. The code in the TrySetMember sets a property or method at the instance level by putting an entry in the items dictionary. Unlike the TryGetMember method, the code does not bother with setting properties and methods at the class level. This is because the TrySetMember method is called when code like self.Spouse = spouse in Listing 8-11 is executed. As you can see, when such code is executed, we want to set the Spouse property of the instance referenced by self. In other words, we want to set the Spouse property at the instance level. If the client code wants to set a property at the class level, then instead of doing something like self.Spouse = spouse, the client code should set the property by using code like the customerClass.Spouse = null in Listing 8-11. This code will cause the TrySetMember method of ExpandoClass, which you will see in a minute, to be called.

In summary, an important point to keep in mind when using the ClassMetaObject and ExpandoClass classes is that when setting a dynamic property or method at the instance level, you need to set it to an instance of ClassMetaObject. If you want to set a dynamic property or method at the class level, you need to set it to an instance of ExpandoClass. However, you can retrieve an instance-level or class-level dynamic property or method by getting it from a ClassMetaObject instance.

Listing 8-12. The ClassMetaObject Class

public abstract class ClassMetaObject : DynamicObject
{
    protected abstract ExpandoClass Class
    {
        get;
    }

    private Dictionary<string, object> items = new Dictionary<string, object>();

    public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        if (items.TryGetValue(binder.Name, out result))
            return true;
        else
            return Class.TryGetMember(binder, out result);
    }
    public override bool TrySetMember(
        SetMemberBinder binder, object value)
    {
        items[binder.Name] = value;
        return true;
    }
}

The ExpandoClass code is similar to that of ClassMetaObject, only simpler. Listing 8-13 shows how the ExpandoClass class is implemented. Like ClassMetaObject, ExpandoClass also derives from DynamicObject and overrides the TryGetMember and TrySetMember methods. ExpandoClass defines a private member variable called items to hold class-level dynamic properties and methods.

Listing 8-13. The ExpandoClass Class

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

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

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

To illustrate how ExpandoClass works in concert with ClassMetaObject, I'll trace how the SetSpouse method is added to the Customer class and then later invoked on a Customer instance. Recall the following code snippet in Listing 8-11:

customerClass.SetSpouse = SetSpouse;
bob.SetSpouse(bob, mary);

In the code snippet, when we assign the SetSpouse delegate to the SetSpouse member of customerClass, because customerClass is an instance of ExpandoClass, the TrySetMember method of ExpandoClass will be invoked and the SetSpouse method will be added to the C# Customer class at the class level. When we call the SetSpouse method on bob, because bob is an instance of ClassMetaObject, the TryGetMember method of ClassMetaObject will be invoked. The TryGetMember method of ClassMetaObject will not find a method by the name SetSpouse because the SetSpouse method was added to bob at the instance level. So the TryGetMember method of ClassMetaObject will proceed to call the TryGetMember method of ExpandoClass and the SetSpouse method we added to the C# Customer class will be retrieved and eventually called.

LINQ Query Provider

So far in this chapter, I have shown how we can add methods to a class and to a particular instance of a class dynamically, completely in C# without any use of a dynamic language such as Ruby or Python. Now we are going to take a detour and look at another kind of metaprogramming technique that is made possible by the DLR. The metaprogramming technique I'll show you is based on DLR Expression, and I will demonstrate it by implementing a custom LINQ query provider. The exciting thing about doing this is that we are going to gradually evolve the custom LINQ query provider into a code-generation framework that utilizes the ClassMetaObject and ExpandoClass classes we built in the previous section. At the end, we'll arrive at a code-generation framework that is in spirit similar to frameworks such as the popular Ruby on Rails.

Understanding the End Goal

In this section we'll build a LINQ query provider. However, our true goal is to understand not the LINQ query provider itself, but rather the DLR Expression metaprogramming the LINQ query provider is based on. As you'll see, DLR Expression allows us to represent code as data. The data is in the form of expression trees that we can easily manipulate using the Visitor design pattern we looked at in Chapter 2. We saw many examples of DLR Expression in Chapter 2. The code here will be similar, except that this time our DLR Expression example is framed in the context of LINQ query providers.

LINQ is a component of the .NET Framework that allows writing code like the following to query a data source:

IEnumerable<Customer> selectedCustomers =
                from c in customers
                where c.FirstName.Equals("Bob") select c;

In the code snippet, the variable customers is the data source from which we want to select customers whose first name is Bob. The variable customers might represent data in database, information in XML files, or a collection of objects in memory. As far as the query is concerned, it doesn't matter whether the underlying data store is a database or an XML file. As long as there is a LINQ query provider that knows how to take our LINQ query and fetch the right data from the underlying data store, our LINQ query will run just fine.

From this little explanation of LINQ, you can see that the two major players in LINQ are queries and query providers. Queries are decoupled from the actual data store. The only link between queries and the actual store is a query provider. A query provider knows how to take queries and execute them against a particular data store. In this section, the custom query provider we will implement is one that executes queries against a collection of in-memory objects.

Implementing the Query Class

The implementation of the custom query provider consists of three main classes: Query<T>, QueryProvider<T>, and QueryExpressionVisitor<T>. The Query<T> class represents the queries that will be processed by our custom query provider as DLR expression trees. The QueryProvider<T> class implements the logic of our custom query provider. QueryProvider<T> is the class that contains the logic for executing instances of the Query<T> class. When a QueryProvider<T> instance executes instances of the Query<T> class, it uses instances of the QueryExpressionVisitor<T> class to manipulate the DLR expression trees of the Query<T> instances. We will now go over the code of the three classes, Query<T>, QueryProvider<T>, and QueryExpressionVisitor<T>. You will see as an example a typical use of DLR Expression as a metaprogramming technique.

Listing 8-14 shows the Query<T> class. To be a class that represents LINQ queries, Query<T> must implement the IQueryable<T> interface. Of the property accessors and interface methods we must implement in order to implement the IQueryable<T> interface, the two most important ones are the Expression property get accessor and the Provider property get accessor. As mentioned previously, the Query<T> class represents queries as DLR expression trees. The Expression property is the evidence of that. The property holds a DLR expression that can have child expressions. Those child expressions can have child expressions, and so on. All together, the expressions form an expression tree that represents a query. As to the Provider property, it's there in Query<T> to decouple queries from the actual data store. It is the link between queries and the actual store.

Listing 8-14. The Query<T> Class

public class Query<T> : IQueryable<T>
{
    private IQueryProvider provider;
    private Expression expression;

    public Query(IQueryProvider provider)
    {
        this.provider = provider;
        this.expression = Expression.Constant(this);
    }

    public Query(IQueryProvider provider, Expression expression)
    {
        if (!typeof(IQueryable<T>).IsAssignableFrom(expression.Type))
            throw new ArgumentException("expression");
        this.provider = provider;
        this.expression = expression;
    }

    Expression IQueryable.Expression
    {
        get { return expression; }
    }

    Type IQueryable.ElementType
    {
        get { return typeof(T); }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return provider; }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>)provider.Execute(expression)).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)provider.Execute(expression)).GetEnumerator();
    }
}

Implementing the QueryProvider Class

Listing 8-15 shows the QueryProvider<T> class. To be a class that represents LINQ query providers, QueryProvider<T> must implement the IQueryProvider interface. The constructor of QueryProvider<T> takes a list of objects as input and assigns that list to the class member variable records. This variable represents the data store our custom query provider works against. I chose to use a list of objects as the data store for the sake of simplicity without losing any generality. Had I chosen a database as the data store, we would need to go through the extra work of setting up a database.

The IQueryProvider interface defines four methods and we implement all of them in QueryProvider<T>. The four methods are the two CreateQuery methods and the two Execute methods. Our implementation of the generic version of the CreateQuery method simply creates an instance of Query<T> and returns it. The non-generic version of the CreateQuery method is implemented to throw a NotImplementedException because the method is not needed in our example. The generic version of the Execute method delegates its work to the non-generic version of the Execute method, which is where the interesting things happen. The non-generic version of the Execute method takes an expression tree that represents a LINQ query as input and executes that query against the data store records. The expression tree that represents a LINQ query can be very complex and can contain expressions that represent where clauses, order-by clauses, group-by clauses, and so on. A practical implementation of the Execute method would need to be able to handle most of those different clauses. For the purpose of our example, it's enough to just handle the where clauses. In fact, all I want the query provider to be able to handle is the following query:

Query<Customer> customers = new Query<Customer>(provider);
from c in customers where c.FirstName.Equals("Bob") select c;

The variable provider in the query is an instance of QueryProvider<T>. The query uses the Customer class that we haven't introduced yet. But, basically, the query invokes our custom query provider to fetch customers whose first name is Bob. The query is constructed using the keywords from, in, where, and select that the C# language provides. Those keywords are just syntactic sugar over a set of underlying methods defined in the System.Linq.Queryable class. When the C# compiler sees those keywords, it translates them into calls to the underlying methods in System.Linq.Queryable. If we don't use the syntactic sugar, we can equivalently express our query with the following code:

Query<Customer> customers = new Query<Customer>(provider);
Queryable.Where<Customer>(customers, c => c.FirstName.Equals(firstName));

The return value of Queryable's Where<T> method is IQueryable<T>. The first input parameter of the Where<T> method is also of type IQueryable<T>. What the Where<T> does internally is very simple. It constructs a MethodCallExpression instance that represents a call to the Where<T> method. The MethodCallExpression instance has two input arguments that are made child expressions of the MethodCallExpression instance. The two input arguments are expressions that represent the first and second input parameters of the Where<T> method. After constructing the MethodCallExpression instance, the Where<T> method creates a new instance of Query<T>. Then it sets the new Query<T> instance's Expression property to the MethodCallExpression instance. Once you understand how our query is represented as a MethodCallExpression instance, it should be easy to understand why the non-generic version of the Execute method is implemented the way it is in Listing 8-15.

In the Execute method, we first check if the input expression that represents the query to execute is a MethodCallExpression. If so, we further check if the MethodCallExpression represents a call to the Where<T> method of the Queryable class. If that's not the case, then the query is outside the scope of what we want our custom query provider to support and therefore we throw a NotSupportedException. If the input expression parameter of the Execute method does represent a call to the Where<T> method of the Queryable class, we use an expression visitor to visit the MethodCallExpression and its descendant expressions. The expression visitor is an instance of QueryExpressionVisitor<T>, whose code is shown Listing 8-16. The expression visitor's job is to retrieve the lambda function that makes up the where clause of our query. In our query, that lambda function is c => c.FirstName.Equals(firstName) and it is stored as a descendant expression under the MethodCallExpression. Once the visitor retrieves the expression representing the lambda function, in line 39 of Listing 8-15 we get that expression from the visitor's Predicate property and use the expression to find matching objects in the class member variable records. Because the LINQ component of the .NET Framework comes with a LINQ query provider called LINQ to Objects for executing LINQ queries against IEnumerable<T> collections such as the class member variable records, line 39 simply uses the LINQ to Objects query provider to find the matching objects. This might strike you as a little absurd because we could have used the LINQ to Objects query provider directly without building a custom query provider that uses the LINQ to Objects provider internally. That's true, except that if we had used the LINQ to Objects query provider directly, I wouldn't be able to use our custom query provider as a typical example of how DLR Expression is used as a metaprogramming technique. Besides, you can think of this use of the LINQ to Objects provider as simplification of a more practical case where we would have more complex logic for querying the data store. Next, let's take a look at how to implement the QueryExpressionVisitor<T> class to retrieve the expression that represents the lambda function in a where clause.

Listing 8-15. The QueryProvider<T> Class

1)   public class QueryProvider<T> : IQueryProvider
2)   {
3)       private IList<T> records;
4)
5)       public QueryProvider(IList<T> records)
6)       {
7)           this. records = records;
8)       }
9)
10)      public IQueryable<T> CreateQuery<T>(Expression expression)
11)      {
12)          if (expression == null)
13)              return new Query<T>(this);
14)          else
15)              return new Query<T>(this, expression);
16)      }
17)
18)      public IQueryable CreateQuery(Expression expression)
19)      {
20)          throw new NotImplementedException();
21)      }
22)
23)      public TResult Execute<TResult>(Expression expression)
24)      {
25)          return (TResult)this.Execute(expression);
26)      }
27)
28)      public object Execute(Expression expression)
29)      {
30)          if (!(expression is MethodCallExpression))
31)              throw new NotSupportedException("The expression needs to be a
    MethodCallExpression");
32)
33)          MethodCallExpression methodCallExpression = (MethodCallExpression) expression;
34)          if (methodCallExpression.Method.DeclaringType == typeof(Queryable)
35)              && methodCallExpression.Method.Name == "Where")
36)          {
37)              QueryExpressionVisitor<T> visitor = new QueryExpressionVisitor<T>();
38)              visitor.Visit(methodCallExpression);
39)              return records.Where<T>(visitor.Predicate);
40)          }
41)          else
42)              throw new NotSupportedException(
43)                        "The expression needs to be a call to the Where method of
    Queryable");
44)      }
45)  }

Implementing QueryExpressionVisitor

To qualify as a DLR expression visitor class, a class must derive from System.Linq.Expressions.ExpressionVisitor. The class ExpressionVisitor defines methods for different expression classes. A subclass of ExpressionVisitor will inherit those methods and override the ones that it wants to provide custom logic for.

In the case of our example, QueryExpressionVisitor<T> overrides the VisitMethodCall method it inherits from ExpressionVisitor. The overridden VisitMethodCall method will be called for every MethodCallExpression node in the expression tree being traversed. Because the lambda expression we want QueryExpressionVisitor<T> to retrieve is a child expression of a MethodCallExpression, we override the VisitMethodCall method in QueryExpressionVisitor<T>. In the overridden VisitMethodCall method, we check whether the method call expression node being visited represents a method call to the Where<T> method of the Queryable class. If so, we proceed to get the second argument of the method call expression because that second argument is the expression that represents the lambda function in a where clause.

One little thing to be mindful of is that the lambda expression we want to retrieve might be wrapped by unary expressions whose node type is ExpressionType.Quote. The reason for quoting a lambda expression is so that when the quoted expression is compiled, it will compile into an expression instead of a lambda function, as would be the case without the quoting. Because of the possible quoting of the lambda expression, the code in Listing 8-16 uses a method called GetPastQuotes to get past the unary expressions to the lambda expression we are interested in. When we get a hold of the lambda expression, we assign it to the Predicate field of QueryExpressionVisitor<T> so that we can use it in the Execute method of QueryProvider<T>.

Listing 8-16. The QueryExpressionVisitor<T> Class

internal class QueryExpressionVisitor<T> : ExpressionVisitor
{
    public Func<T, bool> Predicate;
        
    internal QueryExpressionVisitor()
    {  }

    protected override Expression VisitMethodCall(MethodCallExpression m)
    {
        if (m.Method.DeclaringType == typeof(Queryable) && m.Method.Name == "Where")
        {
            //The second argument of the method call expression is a lambda expression that
serves
            //as the predicate for the 'Where' clause.
            LambdaExpression lambda = (LambdaExpression)GetPastQuotes(m.Arguments[1]);
            Predicate = (Func<T, bool>) lambda.Compile();
        }

        return base.VisitMethodCall(m);
    }

    private Expression GetPastQuotes(Expression expression)
    {
        while (expression.NodeType == ExpressionType.Quote)
            expression = ((UnaryExpression) expression).Operand;
        return expression;
    }
}

This section uses the implementation of a custom query provider to demonstrate the use of DLR Expression as a metaprogramming technique. We saw that queries written in code end up being represented by DLR expressions. This is the concept of code as data in action. The code in this case is the query code from c in customers where c.FirstName.Equals(“Bob”) select c; and the data is the MethodCallExpression and its descendant expressions that represent the query code. We also saw how the DLR expressions are interpreted and executed by a query provider. This is the concept of data as code in action. The data in this case is the DLR expressions and that data is used as code by our custom query provider. The code we write to interpret and execute DLR expressions is a metaprogram. The program that the metaprogram acts on is the code represented by the DLR expressions.

Data Access

Now that we have gone through the implementation of a custom query provider, I am going to show you three ways of using that query provider, as well as the pros and cons of each approach. At the end of this part of the chapter, you will arrive at a primitive code-generation prototype that is in spirit similar to frameworks like Ruby on Rails.

Before we start to look at the three different ways of using our custom query provider, there is some preparation work to do. First, let's define a Customer class like the one in Listing 8-17. The Customer class is straightforward. We will use it as the type of the objects we query.

Listing 8-17. The Customer Class for Trying Out Our Custom Query Provider

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public override string ToString()
    {
        return FirstName + " " + LastName;
    }
}

Next we need some instances of the Customer class to serve as the data source of our LINQ queries. For that, let's create the DataStore class shown in Listing 8-18. The code in Listing 8-18 is pretty simple. It creates a list of Customer instances and uses that list to create an instance of QueryProvider<Customer> whenever the static GetQueryProvider method is called.

Listing 8-18.The DataStore Class That Contains the Data We Will Query Against

public class DataStore
{
    private static IList<Customer> customers = new List<Customer>(new Customer[] {
                        new Customer {FirstName="Bob", LastName="Smith"},
                        new Customer {FirstName="John", LastName="Smith"},
                        new Customer {FirstName="Bill", LastName="Jones"},
                        new Customer {FirstName="Mary", LastName="Jones"},
                        new Customer {FirstName="Bob", LastName="Jones"}});

    public static IQueryProvider GetCustomerQueryProvider()
    {
        return new QueryProvider<Customer>(customers);
    }
}

We have now completed the preparation work and we are ready to see the three ways to use our custom query provider. The first approach does not involve any late binding and therefore will be familiar to those who have worked with LINQ queries before. Let's take a look.

Static Data Access

Because this approach to using our custom query provider does not involve any late binding, I'll refer to it as the static data access approach. In the architecture of a software system, it is not uncommon to have a layer that handles data access. The responsibility of the data access layer is to (a) handle the interactions with data stores such as a database, and (b) decouple the rest of the software system from the specifics of the data stores. For the purpose of our example, let's imagine that we are building the data access layer of a software system. The data access layer will interact with our custom query provider. We want the data access layer to abstract away the fact that we are using a LINQ query provider so that if we ever need to swap out the LINQ query provider and replace it with, say, an object-relational mapping component like NHibernate, the rest of the software system can stay the same.

To achieve the data-access abstraction we want, we can define the interface ICustomerDao shown in Listing 8-19. The interface defines the signature of the FindByFirstName and FindByLastName methods and does not dictate what data store we should use in implementing the interface. We might have a concrete implementation of the ICustomerDao interface that uses a database as the backing data store. We might have another concrete implementation of the ICustomerDao interface that uses in-memory objects as the backing data store. Because the rest of our software system works with the data-store-agnostic ICustomerDao interface, we can swap out one concrete implementation and swap in another and the rest of our software system won't be affected.

Listing 8-19. The ICustomerDao Interface

public interface ICustomerDao
{
    IEnumerable<Customer> FindByFirstName(string firstName);
    IEnumerable<Customer> FindByLastName(string lastName);
}

Listing 8-20 shows the CustomerDao class that implements the ICustomerDao interface by using a LINQ query provider internally. As you can see, the implementation of the FindByFirstName method uses the from, where, in, and select C# language syntactic sugar to construct and return an instance of IQueryable<Customer> as an instance of IEnumerable<Customer>. This is okay because IQueryable<T> derives from the IEnumerable<T> interface. The implementation of the FindByLastName method is similar to that of the FindByFirstName method.

Listing 8-20. The CustomerDao Class

public class CustomerDao : ICustomerDao
{
    private IQueryProvider provider;

    public CustomerDao(IQueryProvider provider)
    {
        this.provider = provider;
    }

    public IEnumerable<Customer> FindByFirstName(string firstName)
    {
        return from c in provider.CreateQuery<Customer>(null)
                where c.FirstName.Equals(firstName)
  select c;
    }

    public IEnumerable<Customer> FindByLastName(string lastName)
    {
        return from c in provider.CreateQuery<Customer>(null)
                where c.LastName.Equals(lastName)
                select c;
    }
}

The code in the CustomerDao class does not run by itself. Listing 8-21 shows an example that creates an instance of CustomerDao and calls the FindByFirstName and FindByLastName methods on the instance. The example is pretty self-explanatory. One thing to note is that when FindByFirstName or FindByLastName returns an IQueryable<Customer> type cast as an IEnumerable<Customer> instance, the LINQ query represented by the IEnumerable<Customer> instance is not executed yet. The LINQ query is executed when the code in Listing 8-21 starts iterating through the IEnumerable<Customer> instance. This is because when that happens, the GetEnumerator method of the Query class will be called. If you look at the code in the GetEnumerator method of the Query class, you will see that there the LINQ query is executed by a query provider.

Listing 8-21. An Example of Using the CustomerDao Class

private static void RunCustomerDaoExample()
{
    CustomerDao customerDao = new CustomerDao(DataStore.GetCustomerQueryProvider());
    IEnumerable<Customer> customers = customerDao.FindByFirstName("Bob");

    foreach (var item in customers)
        Console.WriteLine(item);

    customers = customerDao.FindByLastName("Jones");

    foreach (var item in customers)
        Console.WriteLine(item);
}

Even though the ICustomerDao interface and the CustomerDao class provide a nice abstraction of the underlying data access details to the rest of our software system, one downside of this approach is the amount of boilerplate code we need to write. For each property, such as FirstName in the Customer class, we need to define a method like FindByFirstName method in ICustomerDao and implement that method in CustomerDao. It would be nice if we could freely define properties like FirstName and LastName in Customer and the rest of the data access code, such as the FindByFirstName and FindByLastName methods, would just be there automatically. Well, that's what our next approach is going to do.

Dynamic Data Access

Let's look at the second approach for using our custom query provider in the data access layer. In this approach, we will leverage the metaprogramming facilities made possible by DLR Expression and dynamic objects so that methods like FindByFirstName and FindByLastName don't need to be manually coded. Because the approach we are going to look at uses the late-binding capability of the DLR, I'll refer to it as the dynamic data access approach. Listing 8-22 shows the data access layer class, DynamicDao<T>, that has the logic for responding to invocations of the FindByFirstName and FindByLastName methods, without us needing to write those methods manually. The idea of the dynamic data access approach is that we don't define and implement methods like FindbyFirstName and FindByLastName in DynamicDao<T>. Instead we make DynamicDao<T> a subclass of DynamicObject. So when the methods FindbyFirstName and FindByLastName are invoked on an instance of DynamicDao<T>, the TryInvokeMember method of DynamicDao<T> will be invoked to handle the late binding of those method invocations. In the body of the TryInvokeMember method, we implement the late-binding logic in such a way that if the method invoked is FindByFirstName or FindByLastName, we return an IQueryable<Customer> instance. The IQueryable<Customer> instance when executed will return only customers whose first name (or last name) matches the queried first name (or last name).

In Listing 8-22, the code in the TryInvokeMember method first gets the invoked method name from the Name property of the binder parameter. If the invoked method is FindByFirstName, the Name property of the binder parameter will be the string “FindByFirstName”. So we strip out “FindBy” and obtain the property name “FirstName”. If the invoked method is FindByFirstName, we want the TryInvokeMember method to return as the result an IQueryable<Customer> instance equivalent to the IQueryable<Customer> instance returned by the FindByFirstName method of the CustomerDao class we saw in the previous section. The bulk of the code in the TryInvokeMember method is to construct a DLR expression that represents the predicate lambda function to use in the where clause of the query object we aim to construct. The predicate lambda function expressed in C# code will look something like this:

(T x) => x.[propertyName].Equals(arg);

In this C# code, if the TryInvokeMember function is invoked to find customers whose first name is “Bob”, then T will be the type Customer, [propertyName] will be FirstName and arg will be “Bob”. The predicate lambda function in C# maps very nicely to the predicate expression we try to construct in Listing 8-22. The x.[propertyName] part in the C# lambda function above is a property member access and it maps to the Expression.MakeMemberAccess method call in line 18. In the body of the C# lambda function, the call to the Equals method maps to the Expression.Call method call in line 17. The whole C# lambda function maps to the Expression.Lambda method call in line 16. The C# lambda function has one input parameter x,  which maps to the parameter variable created in line 14.

Once the predicate expression is constructed, we pass it as the input parameter to the Where<T> method call in line 26 so that the query we return as the late-binding result will have a where clause with the desired predicate for matching customers.

Listing 8-22. The DynamicDao<T> Class

1)   public class DynamicDao<T> : DynamicObject
2)   {
3)       private IQueryProvider provider;
4)
5)       public DynamicDao(IQueryProvider provider)
6)       {
7)           this.provider = provider;
8)       }
9)
10)      public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args,
11)          out object result)
12)      {
13)          String propertyName = binder.Name.Substring(6); //6 is the length of 'FindBy'
14)          ParameterExpression parameter = Expression.Parameter(typeof(T));
15)          PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);
16)          Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(
17)              Expression.Call(
18)                  Expression.MakeMemberAccess(
19)                      parameter,
20)                      propertyInfo),
21)                  propertyInfo.PropertyType.GetMethod("Equals", new Type[]
   {typeof(object)}),
22)                  Expression.Constant(args[0])),
23)              parameter);
24)
25)          Query<T> query = new Query<T>(provider);
26)          result = query.Where<T>(predicate);
27)          return true;
28)      }
29)  }

Listing 8-23 shows an example that creates an instance of DynamicDao<Customer> and calls the FindByFirstName and FindByLastName methods on the instance. You can run the code and verify that everything works as expected.

Listing 8-23. An Example of Using the DynamicDao<T> Class

private static void RunDynamicDaoExample()
{
    dynamic customerDao = new DynamicDao<Customer>(DataStore.GetCustomerQueryProvider());
    IEnumerable<Customer> customers = customerDao.FindByFirstName("Bob");

    foreach (var item in customers)
        Console.WriteLine(item);

    customers = customerDao.FindByLastName("Jones");

    foreach (var item in customers)
        Console.WriteLine(item);
}

The dynamic data access approach shown in this section frees us from having to define and implement methods like FindByFirstName and FindByLastName for each property in the Customer class. However, the code in the TryInvokeMember method of DynamicDao<T> looks pretty ad hoc to me. The code works for our simple example, but in practical cases, we could have different kinds of methods than the “FindBy” methods we want to bind late. If we handle those practical cases the way we do in DynamicDao<T>, we will have to parse those different kinds of method names and use those names to guide the program's execution path. We would need to refactor the code in DynamicDao<T> substantially so that we don't put all the late-binding logic in the TryInvokeMember method. Essentially, the problem with DynamicDao<T> as it is implemented is the lack of a well-structured mechanism for routing a method invocation to the right late-binding logic. One way to amend the issue is to refactor the code in DynamicDao<T>. Another way is to leverage the ClassMetaObject and ExpandoClass classes we built earlier in this chapter. The next section shows how to do that.

Generated Data Access

We will now show the third approach for using our custom query provider in the data access layer. In this approach, we will leverage the metaprogramming features of the ClassMetaObject and ExpandoClass classes we built earlier so that we have a well-structured mechanism for routing a method invocation to the right late-binding logic. The idea of this approach is to generate methods like FindByFirstName and FindByLastName at runtime and add those methods to a data access layer class. With this approach, we no longer need to parse method names and use those names to pick the right late-binding logic, as we did in the DynamicDao<T> class. Because the approach demonstrated in this section is based on the concept of code generation, I will refer to it as the generated data access approach.

Listing 8-24 shows the class GeneratedDao<T> to which we will add the FindByFirstName and FindByLastName methods. The code in Listing 8-24 might look complicated at first, but it's actually quite simple once you understand the code structure. First, the class GeneratedDao<T> derives from ClassMetaObject. That means we can add new methods to GeneratedDao<T> at the class level or instance level. New methods added to GeneratedDao<T> at the class level are added to the static _class variable. Our goal is to add a “FindBy” method for each property in type T to GeneratedDao<T> at the class level. To achieve that, in Listing 8-24 we define the AddMethods method that loops through all the properties of T and calls the AddMethodForProperty method for each property. AddMethodForProperty calls the CreateNewMethodExpression method to get an expression that represents the new method to be added for a property. Using FindByFirstName as an example, the expression returned by CreateNewMethodExpression when compiled will be equivalent to the following C# code:

Func<String, IEnumerable<Customer>> FindByFirstName =
    (firstName) =>
    {
        IQueryable<Customer> query = provider.CreateQuery<Customer>(null);
        return query.Where(c => c.FirstName.Equals(firstName));
    };

The equivalent C# code is the same as the FindByFirstName method we saw in CustomerDao. Here we are just implementing the same method in terms of DLR expressions. The CreateNewMethodExpression method internally calls the GetWhereMethodInfo method to get a System.Reflection.MethodInfo instance for the Where method of the Queryable class. CreateNewMethodExpression calls the CreatePredicateExpression to get the expression that represents the predicate (the c => c.FirstName.Equals(firstName) in the above code snippet) in the query's where clause. Once AddMethodForProperty gets the lambda expression returned by the CreateNewMethodExpression method, it constructs an expression that adds the lambda expression as a new method to _class by calling the TrySetMember method on _class.

Listing 8-24. The GeneratedDao<T> Class

public class GeneratedDao<T> : ClassMetaObject
{
    private static ExpandoClass _class = new ExpandoClass();

    protected override ExpandoClass Class
    {
        get { return _class; }
    }

    private IQueryProvider provider;
    private MethodInfo whereMethod = null;

    public GeneratedDao(IQueryProvider provider)
    {
        this.provider = provider;
        AddMethods();
    }

    private void AddMethods()
    {
        PropertyInfo[] properties = typeof(T).GetProperties();
        foreach (PropertyInfo propertyInfo in properties)
            AddMethodForProperty(propertyInfo);
    }

    private void AddMethodForProperty(PropertyInfo propertyInfo)
    {
        LambdaExpression newMethod = CreateNewMethodExpression(propertyInfo);

        SetMemberBinder binder = new SimpleSetMemberBinder("FindBy" + propertyInfo.Name, false);
        Expression addMethodExpression = Expression.Call(
            Expression.Constant(_class),
            _class.GetType().GetMethod("TrySetMember"),
            Expression.Constant(binder), newMethod
        );

        Func<bool> func = Expression.Lambda<Func<bool>>(addMethodExpression).Compile();
        func();
    }

    private Expression<Func<T, bool>> CreatePredicateExpression(
        PropertyInfo propertyInfo, ParameterExpression argExpression)
    {
        //predicate = (T x) =>
        //{
        //   x.propertyName.Equals(arg);
        //}

        ParameterExpression parameter = Expression.Parameter(typeof(T));
        return Expression.Lambda<Func<T, bool>>(
            Expression.Call(
                Expression.MakeMemberAccess(
                    parameter,
                    propertyInfo),
                propertyInfo.PropertyType.GetMethod("Equals", new Type[] { typeof(object) }),
                argExpression),
            parameter);
    }

    private MethodInfo GetWhereMethodInfo()
    {
        if (whereMethod != null)
            return whereMethod;

        MethodInfo[] allMethods = typeof(Queryable).GetMethods(
        BindingFlags.Public | BindingFlags.Static);
        foreach (var method in allMethods)
        {
            if (method.Name.Equals("Where"))
            {
                ParameterInfo[] parameters = method.GetParameters();
                Type[] genericTypes = parameters[1].ParameterType.GetGenericArguments();
                if (genericTypes[0].GetGenericArguments().Length == 2)
                    whereMethod = method;
            }
        }

        whereMethod = whereMethod.MakeGenericMethod(new Type[] { typeof(T) });
        return whereMethod;
    }

    private LambdaExpression CreateNewMethodExpression(PropertyInfo propertyInfo)
    {
        ParameterExpression argExpression = Expression.Parameter(propertyInfo.PropertyType);
        Expression<Func<T, bool>> predicate = CreatePredicateExpression(propertyInfo,
argExpression);

        //provider.CreateQuery<Customer>(null);
        Expression queryExpression = Expression.Call(
            Expression.Constant(provider), "CreateQuery",
            new Type[] { typeof(T) }, Expression.Constant(null, typeof(Expression)));

        //query.Where(c => c.FirstName.Equals(firstName));
        Expression body = Expression.Call(null,
            GetWhereMethodInfo(), queryExpression,
            predicate);

        return Expression.Lambda(body, argExpression);
    }
}

To try out the GeneratedDao<T> class, you can run the code in Listing 8-25. This code creates an instance of GeneratedDao<Customer> and calls the FindByFirstName and FindByLastName methods on the instance.

Listing 8-25. An Example of Using the GeneratedDao<T> Class

private static void RunGeneratedDaoExample()
{
    dynamic customerDao = new GeneratedDao<Customer>(DataStore.GetCustomerQueryProvider());

    IEnumerable<Customer> customers = customerDao.FindByFirstName("Bob");
    foreach (var item in customers)
        Console.WriteLine(item);
    customers = customerDao.FindByLastName("Jones");
    foreach (var item in customers)
        Console.WriteLine(item);
}

Summary

This chapter gives an overview of metaprogramming and then shows some exciting ways you can use metaprogramming in your .NET applications. Thanks to the DLR, your applications don't need to use dynamic languages in order to benefit from the metaprogramming techniques traditionally available only in dynamic languages. In particular, this chapter implements two classes, ClassMetaObject and ExpandoClass, that serve as the foundation of other marvelous applications of metaprogramming. As an example of the wonderful things you can do with ClassMetaObject and ExpandoClass, we use those classes in building a code-generation framework that is in spirit similar to frameworks such as the popular Ruby on Rails. Because the code-generation framework is just an example, it omits a lot of details and shows only the concept. Though I can't promise, I have plans in my mind to continue the development of the code-generation example shown in this chapter, and to experiment with model-driven development and domain-specific language development with it. You are welcome to head over the dpier project web site at http://code.google.com/p/dpier/ and check out the progress.

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

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