New in PostSharp 2.1: Navigating Code Relationships

by Gael Fraiteur on 22 Jul 2011

In my previous post, I introduced the new class ReflectionSearch and two of its methods: GetCustomAttributesOfType and GetCustomAttributesOnTarget. These methods give access to PostSharp’s internal repository of custom attributes.

Today, I would like to cover the second feature of ReflectionSearch: querying the relationships between elements of code. Three kinds of relationships are supported:

  • type inheritance (classes and interfaces),
  • usage in statements (a type, method, or field is used inside a method body),
  • member type,
  • any of the above, but as a type element (described below).

Navigating Type Inheritance

The system reflection API makes it easy to navigate from a child class to its parents, but the opposite is not true: if you want to retrieve all classes implementing a given interface in the assembly, you have to enumerate all types in this assembly, then enumerate all implemented interfaces, and do a recursion on the parent type. As was the case in the previous post with custom attributes, PostSharp indexes the type hierarchy for internal use. From PostSharp 2.1, ReflectionSearch exposes this feature to high-level aspects, and it comes with no performance cost.

The method ReflectionSearch.GetDerivedTypes does what it says, with one remark: by default, it returns only first-level derived types, i.e. it does not return derived types of derived types. If you want a deep search, you have to specify the option ReflectionSearchOptions.IncludeDerivedTypes.

Note that the method does not return an array of types, but of TypeInheritanceCodeReference. This object provides information about the base type. You may think the base type is always what you passed in the argument, but it isn’t always true. Consider the following code:

class GenericCollection<T> : ICollection<T> {}

class IntCollection : GenericCollection<int> {}

TypeInheritanceCodeReference[] derivedTypes = 
ReflectionSearch.GetDerivedTypes( typeof(ICollection<>);

 

In this case, GetDerivedTypes would return two pairs: (GenericCollection<T>, ICollection<T>) and (IntCollection, ICollection<int>). In each case, the base type is a different instance of the generic type.

Navigating Usage

As a part of the post-compilation process, PostSharp scans all method bodies and builds a bi-directional map of using/used-by relationships, i.e. it determines which fields, methods or type are used in statements and expressions of a method body, and creates an index. This functionality is now available inside PostSharp.dll and is exposed on two methods:

  • ReflectionSearch.GetDeclarationsUsedByMethod returns the set of fields, methods and types used by a given method.
  • ReflectionSearch.GetMethodsUsingDeclaration returns all methods using a given field, method, or type.

As you can note, it is not possible to query references to properties and events. Indeed, properties and events are compiler “syntactic sugar” and are never referred to by instructions at MSIL level. If you want to query properties or events, you have to query each accessor separately (add, remove, get, set).

These methods return an array of MethodUsageCodeReference. This object contains both ends of the relationship, as well as a bit mask telling which instructions the declaration was used with – so you can distinguish between a get or a set operation on a field.

Navigating Member Types

The last thing you can do with ReflectionSearch is to get a list of all members of a given type. By member, I mean a field, a property, an event, a parameter, or a return-value parameter.

This feature is exposed on ReflectionsSearch.GetMembersOfType.

Note that PostSharp does not use this feature internally, so there is an additional performance cost in calling that method. The first time you invoke GetMembersOfType, the assembly will be scanned and indexed (using PostSharp’s internal APIes, not System.Reflection).

Type Elements

By default, all methods of ReflectionSearch match the exact type passed as an argument (or match derived types if ReflectionSearchOptions.IncludeDerivedTypes is specified). However, there are cases where you want a larger choice.

Consider the following code:

class Bar {}

class Foo : ICollection<Bar>
{
   Bar[] array;

   void ICollection<Bar> FooBar()
   {
     return new List<Bar>();
   }
}

var derivedTypes = ReflectionSearch.GetDerivedTypes( typeof(Bar), options );
var members = ReflectionSearch.GetMembersOfType( typeof(Bar), options );
var usages = ReflectionSearch.GetMethodsUsingDeclaration( typeof(Bar), options );

 

With the default value of options, the three methods return an empty set. Indeed, there is no type derived from Bar, no field or return value of type Bar, and no instruction using Bar. However, if you include the option ReflectionSearchOptions.IncludeTypeElement, you will get:

  • as derived type: Foo,
  • as members: the field array and the return value of FooBar, and
  • as usages: the method FooBar.

The IncludeTypeElement option asks PostSharp to index at a much deeper level. Pay attention: this comes at a performance cost. PostSharp does not use this feature internally, so the first time you use the option, it will have to scan all type signatures and build an index.

Summary

The new ReflectionSearch class allows you to make complex queries over System.Reflection. Most of the times, the features are already used internally by PostSharp, so they come at minimal performance cost.

The ReflectionSearch class is accessible at build time only. The primary use case is to write more powerful logic for IAspectProvider or MethodPointcut. The second use case is to validate your code. And this is for another blog post.

Happy PostSharping!

-gael