Query Editor: Writing Queries, Part 1

by Jason Haley 9. November 2009 07:12

In my previous entries on the Query Editor, the focus has been on using the query editor and its various parts not solely on writing queries.  In this entry and the next I am going to change the focus to be just on creating queries to solve a problem, such as finding interface implementations or searching the IL for certain things like box instructions.

Problem Description

Have you ever been using Reflector and navigated to a method just to  find out that it is on an interface … leaving you to go back and figure out what class implements that interface - then look up the method on that class? 

Wouldn’t it be easier if once you discovered a method is on an interface, you could just right click it and get a list of all the implementations of that method?  Writing the query to get this list is what this entry will focus on.

We’ll take small steps to learn how to get the final query but first we’ll start with getting a list of the interfaces in an assembly, then we’ll create a query to find all implementations of an interface and finally we’ll create a query to search through all of the loaded assemblies for a specific interface method implementation.  In the process of taking these steps, you’ll be exposed to some of the Reflector API that can be used for writing your own queries.

Writing Queries

Before getting into the writing some queries, let me mention some of the Reflector API that we will be using in these queries.

In Getting Started with Query Editor and Query Editor Making Queries Available on Menus I mentioned the AssemblyManager and how changing the Query Type effects the type of the ActiveItem property. Figure 1 below shows the AssemblyManager‘s hierarchy of collections:

image_thumb1
Figure 1
If you are familiar with System.Reflection.Assembly, you will see a similarity in the hierarchy between Assembly –> Module –> Type –> Member (Field, Method, Property, Event).   In Reflector’s API, the AssemblyManager holds all of the loaded assemblies and exposes them through the Assemblies property.  Since technically a .Net assembly can have more than one module, there is a collection of modules on each IAssembly interface.  That means all types in an assembly are accessed through the Types property of an IModule. As shown in Figure 1, the Types property is a collection of ITypeDeclaration.
 
image_thumb5
Figure 2
 
Figure 2 above, shows the ITypeDeclaration interface, which has a bool property named Interface.  This property is like the System.Type’s property IsInterface that indicates whether the type is an interface or not.
 
Now that you know a little more about the Reflector API’s objects, lets look at what it takes to create a list of all the interfaces in a selected assembly.

Example 1 – List Interfaces in a Selected Assembly

This example walks through how to get a listing of all interfaces in an assembly … after all we need to find a couple of good interfaces to test our final query with.

The scope of this query is going to be a selected assembly, this means the Query Type will be ‘Assembly’.  With an ‘Assembly’ Query Type, our query text can access the properties of an IAssembly using the underlying ActiveItem property.  In this case we will want to enumerate the Types property exposed by the IModule (remember we have to use the Cast<TResult> method due to the Reflector collections not being friendly with LINQ) which we access with the Modules property of the IAssembly.  Once we are enumerating a list of ITypeDeclaration objects we want to filter the results to only be where the Interface property is true.  The query text will look like this:
 
from m in ActiveItem.Modules.Cast<IModule>()
from t in m.Types.Cast<ITypeDeclaration>()
where t.Interface == true
select t
 
Create and Save the Query
In order to create and save the query, follow these steps.  In the later examples I won’t detail these steps.
 
1.  Open Query Editor (Tools –> Query Editor –> Display Query Editor)
2.  Copy the query text from the above table into the Query Editor, like shown in Figure 3 below.
image_thumb2
Figure 3
 
3.  Click the Save button and fill in the query information, like shown in Figure 4 below.
image_thumb3
Figure 4
4.  Click OK
 
Now when you right click on an assembly in the assembly list, you should get a ‘List Interfaces’ menu item on the Run Query submenu.  When you run that query, you should get a listing of all the interfaces in that assembly.
 
Example 2 – Find Types that Implement an Interface
In all fairness, you can already find all types that implement an interface with Reflector by looking at the ‘Derived Types’ of an interface … but let’s forget about that for right now.  What we want to do is learn how to write a query that will look through all the types in all of the loaded assemblies for types that implement a selected interface.
 
Just to keep things simple for this example, we will assume that an interface with the same name as the selected type’s is good enough for a match.  In reality you want to also include the namespace as well, which would require a call to resolve the type – something I don’t want to get into yet.
 
In Example1, the scope of our query was only a selected assembly, in this query we want to look through all assemblies.  An example of looping through all the assemblies was shown in Getting Started with Query Editor where we listed all of the assembly names in the AssemblyManager (shown below):
 
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
select a.Name
 
So if you take the first line of this query and rework the query from Example 1, like this (notice the second line has been changed from using ActiveItem to the IAssembly named a) – differences are highlighted in yellow:
 
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t in m.Types.Cast<ITypeDeclaration>()
where t.Interface == true
select t
 
This query lists all of the interfaces for all of the types in all of the loaded assemblies.  We now need to change it a little to look through each type’s Interfaces property for an interface of a specific name.
 
In Figure 2 above you can see the ITypeDeclaration has a property named Interfaces which is of type ITypeReferenceCollection.  This collection holds references to any interface the type implements.  We can use that to add another line to our query to look at the ITypeReference objects in that Interfaces property.  While we are at it, we can remove the filter on the Interface == true and add a test for a name (I included an interface that is in the System.Windows.Forms namespace – so you will need that assembly loaded in your Reflector for it to return a result).
 
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t in m.Types.Cast<ITypeDeclaration>()
from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == "IButtonControl"

select t

If you run this query you should get a couple of results showing the query will successfully locate types that implement the IButtonControl interface.  Now we want to change the query to get the interface name from the selected type in Reflector instead of hard coding it.  To do this we need to do two things:
  1. Change the Query Type to ‘Type’ which you do on the Configure dialog – by clicking on the configure button (image_thumb[1] ).
  2. Use the ActiveItem.Name for the interface name comparison
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t in m.Types.Cast<ITypeDeclaration>()
from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ActiveItem.Name
select t

We now have a query that will find all the types that implement a selected interface … but what about nested types?  For example:  if you select System.Windows.Forms.IButtonControl the query works fine, but if you select System.Windows.Forms.ICommandExecutor it doesn’t return anything.
 
If you look at the Derived Types of the ICommandExecutor interface, you’ll see that System.Windows.Forms.MenuItem.MenuItemData implements it, however it is a nested type of the MenuItem type.  If you look at Figure 2 above, you’ll notice there is a property on the ITypeDeclaration interface named NestedTypes, this is where we can find the MenuItemData type that the query isn’t currently locating.
 
So far the easiest way I’ve found to add a search of this NestedTypes property as well as the Types is to copy the query and use a Union to return the results of both queries.  I’ll break this into two steps.  First, I’ll edit the query to look at the nested types:
 
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t1 in m.Types.Cast<ITypeDeclaration>()
from t in t1.NestedTypes.Cast<ITypeDeclaration>()

from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ActiveItem.Name
select t

If you run this query for the ICommandExecutor interface, it will work … but it won’t work for the IButtonControl … so let’s just Union the two together:
 
(from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t in m.Types.Cast<ITypeDeclaration>()
from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ActiveItem.Name
select t).Union(
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t1 in m.Types.Cast<ITypeDeclaration>()
from t in t1.NestedTypes.Cast<ITypeDeclaration>()
from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ActiveItem.Name
select t)

Now the query will work for the two test interfaces: System.Windows.Forms.IButtonControl and System.Windows.Forms.ICommandExecutor – it now finds all types and nested types that implement a selected interface.  This leads us to the next example, finding a selected interface’s method.
 
Example 3 – Find Implementations of an Interface’s Method
In this example we are going to build on what we learned in Example 2 in order to do the following:
  1. Find out what interface the selected method is on
  2. Search all of the loaded assemblies and find the types that implement the interface (this we learned in Example 2)
  3. Locate the method on those types that has the same name as the selected method and return it
Let’s start with finding out what interface the selected method is on.  Getting the selected method as an IMethodDeclaration is provided by the ActiveItem when the Query Type is set to ‘Method’ (which we want), but how do we get the interface the selected method is on?  The key to finding this is shown in inheritance tree for IMethodDeclaration and the IMemberReference interfaces shown in Figure 5 and Figure 6 below:
image_thumb7
image_thumb6
Figure 6
Figure 5  
 
Since IMethodDeclaration inherits IMemberReference, you can access the type the method is on by using the DeclaredType property.  This property is an IType so you will need to specifically cast it to use it in a query.  Building on one of Example 2’s queries (I’m not using the last one for simplicity right now), we can change the filter to match on the selected method’s declaring type, like this:
 
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t in m.Types.Cast<ITypeDeclaration>()
from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ((ITypeReference)ActiveItem.DeclaringType).Name
select t
 
If you run this query on the NotifyDefault method on the IButtonControl interface we were working with in Example 2, the results will be a list of type names.
 
Next step is to find the method in the implementing type that  matches the selected method’s name.  To do this, we need another from clause to loop through the methods and a filter to look for methods matching the selected method’s name (and change the select to return the method instead of the type).
 
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t in m.Types.Cast<ITypeDeclaration>()
from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ((ITypeReference)ActiveItem.DeclaringType).Name
from mt in t.Methods.Cast<IMethodDeclaration>()
where mt.Name == ActiveItem.Name
select mt

If you run this query on the NotifyDefault method on the IButtonControl interface you will now get one result.  This is good news and bad news.  The good news is: it works – we now have a query that will return a list of methods that implement the selected interface method.  The bad news is: there should be two results.
 
We are missing a result due to the way we match on method names.  Using only the method name won’t match any method that is an explicit implementation – for this you will need to match on the full name of the method.  This can easily be added to the query with an Or clause:
 
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t in m.Types.Cast<ITypeDeclaration>()
from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ((ITypeReference)ActiveItem.DeclaringType).Name
from mt in t.Methods.Cast<IMethodDeclaration>()
where mt.Name == ActiveItem.Name
|| mt.Name == ((ITypeReference)ActiveItem.DeclaringType).Namespace
+ "." + ((ITypeReference)ActiveItem.DeclaringType).Name
+ "." + ActiveItem.Name

select mt
 
If you run this query again on the NotifyDefault method, you will get two results … but if you run it on the Execute method of the ICommandExecutor interface (like we did in Example 2) you will see the query doesn’t work on nested types yet.  To make the query work on nested types, you can make the same edit as in Example 2 by creating another query that looks at the type’s NestedTypes property and Union the results together like shown below:
 
Create a query that will look at the NestedTypes property:
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t1 in m.Types.Cast<ITypeDeclaration>()
from t in t1.NestedTypes.Cast<ITypeDeclaration>()

from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ((ITypeReference)ActiveItem.DeclaringType).Name
from mt in t.Methods.Cast<IMethodDeclaration>()
where mt.Name == ActiveItem.Name
|| mt.Name == ((ITypeReference)ActiveItem.DeclaringType).Namespace
+ "." + ((ITypeReference)ActiveItem.DeclaringType).Name
+ "." + ActiveItem.Name
select mt

Put the two queries together and Union the results
(from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t in m.Types.Cast<ITypeDeclaration>()
from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ((ITypeReference)ActiveItem.DeclaringType).Name
from mt in t.Methods.Cast<IMethodDeclaration>()
where mt.Name == ActiveItem.Name
|| mt.Name == ((ITypeReference)ActiveItem.DeclaringType).Namespace
+ "." + ((ITypeReference)ActiveItem.DeclaringType).Name
+ "." + ActiveItem.Name
select mt).Union(
from a in AssemblyManager.Assemblies.Cast<IAssembly>()
from m in a.Modules.Cast<IModule>()
from t1 in m.Types.Cast<ITypeDeclaration>()
from t in t1.NestedTypes.Cast<ITypeDeclaration>()
from i in t.Interfaces.Cast<ITypeReference>()
where i.Name == ((ITypeReference)ActiveItem.DeclaringType).Name
from mt in t.Methods.Cast<IMethodDeclaration>()
where mt.Name == ActiveItem.Name
|| mt.Name == ((ITypeReference)ActiveItem.DeclaringType).Namespace
+ "." + ((ITypeReference)ActiveItem.DeclaringType).Name
+ "." + ActiveItem.Name
select mt)

Now like in Example 2 the query will work for the methods on the two test interfaces: System.Windows.Forms.IButtonControl and System.Windows.Forms.ICommandExecutor.  We now have the final query that allows us to find all the implementations of an interface’s method.

Summary

This entry walked through building three different queries that use the Reflector API to solve a common usage problem (dead-ending on an interface method) and provided some insight into useful parts of the Reflector API for future queries.

In the next entry, I’ll walk through some examples of queries that look at a method body’s IL instructions to do things like count how many box instructions are in a method and what methods call a selected method.

Comments (0) | Post RSSRSS comment feed |

Categories:
Tags:

Comments are closed