Strongly Typed Navigation
While it's possible to navigate a syntax tree using just the parent, sibling and child nodes, it's a rather low level approach to the problem. If your current node is a class declaration, it's a lot of leg work to get at all of the method declarations. Similarly, from a method declaration, it's not convenient to walk the Parent
nodes looking for the class declaration.
Fortunately, ReSharper provides strongly typed navigation to make this a lot more convenient and intuitive.
Navigating down the tree
Navigating down to child nodes is made very easy by derived interfaces of ITreeNode
. All nodes in the tree implement ITreeNode
, and also derive from it to provide a strongly typed API. For example, C# method declarations implement IMethodDeclaration
, which itself derives from ITreeNode
. If we take a look at some of the members:
Each of these properties is a strongly typed accessor to get to the ITreeNode
that represents the attributes on the method, or the node that represents the method's name. Internally, the nodes are found by walking down the tree, looking for nodes of a particular type. They are then downcast to the strongly typed, ITreeNode
derived interfaces.
Collections
Similarly, collections can be represented. Let's take a look at a simplified class declaration:
Here we can see that a class has a collection of method declarations. It is represented in two ways, firstly as an instance of TreeNodeCollection<T>
and secondly as a TreeNodeEnumerable<T>
. Both can be iterated over using foreach
, or LINQ queries. The major difference is that TreeNodeCollection<T>
implements IList<T>
, while TreeNodeEnumerable<T>
only implements IEnumerable<T>
. This means that the MethodDeclarationsEnumerable
property is more lightweight that the MethodDeclarations
property, but at the cost of flexibility.
The MethodDeclarations
property creates a new instance of TreeNodeCollection<T>
each time it's accessed. The child nodes are iterated up-front, and passed to the collection as an array. The collection provides all the features of an implementation of IList<T>
(although it is a read-only list), so it can be iterated sequentially or randomly. It also provides a number of optimised LINQ queries, such as Any
, Reverse
and ToList
.
The MethodDeclarationsEnumerable
property, however, simply creates an instance of TreeNodeEnumerable<T>
(again, each time it's accessed), but the nodes are not collected until the enumerable is enumerated - it is lazily evaluated, and does not store the whole list. This makes it more efficient than TreeNodeCollection<T>
.
Navigating up the tree
ReSharper also provides a strongly typed means of navigating up the tree, which is preferable to manually walking up the chain of Parent
properties. Generally speaking, if a node exposes strongly typed accessors, there will be a complementary navigator class.
For example, the IXmlTag
node exposes a Header
property of type IXmlTagHeader
, providing us strongly typed navigation down the tree (from a node representing a complete xml tag to the node representing the opening part of the tag). The XmlTagNavigator
static class gives us the opposite - strongly typed navigation up to an IXmlTag
from various child nodes.
The classes follow a simple naming pattern. The navigator class is named after the node you're trying to navigate to, and the name ends in the word Navigator
, e.g. XmlTagNavigator
is trying to navigate to an instance of IXmlTag
. The methods all being with GetBy
and finish with the type of the node that should be passed in. So, XmlTagNavigator.GetByAttribute
is trying to navigate to an IXmlTag
, from an IXmlAttribute
.
It should also be pointed out that you can use the navigator classes in combination. For example, to get to a class declaration from an if
statement in a C# file:
It isn't necessary to check for null before passing a node into the navigator classes - they explicitly allow null to be passed in, and will return null if it can't find the appropriate parent.