.NET Tools
Essential productivity kit for .NET and game developers
Introducing ReSharper 5.0: Call Tracking
Previous posts introducing ReSharper 5.0:
There’s a cool new feature in ReSharper 5.0 called Call Tracking (or, alternatively, Call Hierarchy). Basically, it’s a convenient way to perform an all-out Find Usages or Go To Declaration. You can access it by choosing ReSharper | Inspect | Outgoing Calls or ReSharper | Inspect | Incoming Calls.
There’s also Inspect This, a new shortcut to Call Tracking, Value Tracking, and Type Hierarchy features: Ctrl+Shift+Alt+A.
At first we thought of comparing our Call Tracking to Call Hierarchy in Visual Studio 2010. However, it turned out that the VS 2010 version is just not up to par: it doesn’t support events, interfaces, closures and a few other things. It offers no help at all with the use cases we present here. So we’re only going to talk about Call Tracking in ReSharper 5.0.
Events
Let’s search for an outgoing call from Foo
(ReSharper | Inspect | Outgoing Calls):
using System;
public class C2
{
public event EventHandler E = (sender, args) => Subscription_In_Initializer();
static void Subscription_In_Initializer()
{
}
void Foo()
{
E(this, EventArgs.Empty);
}
}
class C3
{
void Bar()
{
new C2().E += Subscription_In_Method;
}
void Subscription_In_Method(object sender, EventArgs e)
{
}
}
Pretty self-explanatory. ReSharper easily finds all subscriptions to E
and displays them as possible calls. Nothing too special, but handy for sure.
Generics
Consider this code sample:
public abstract class Base<T>
{
public void Do(T value)
{
DoImplementation(value);
}
protected abstract void DoImplementation(T value);
}
public class Concrete1 : Base<int>
{
protected override void DoImplementation(int value)
{
}
}
public class Concrete2 : Base<string>
{
protected override void DoImplementation(string value)
{
}
}
Now let’s look at outgoing calls from Base.Foo
:
Speaks for itself.
Now, let’s add a Main
class and try searching for outgoing calls from Foo
:
class Main { void Foo() { Concrete2 c = null; // null so that we don't clutter the call tree c.Do("string"); } }
Concrete1.DoImplementation
doesn’t show up anymore! That’s because ReSharper looked at the type parameters and realized that Base.Do
will be called with T->string
(see the second line of results: Base<string>.Do
— string
means we’re calling a method that substitutes a specific type). Accordingly, ReSharper filtered out Concrete1
because it uses inheritance with the substitution T->int
.
Now let’s look at a slightly more complex, yet very vital example using the Visitor
pattern. Let’s search for incoming calls from ConcreteVisitor1.VisitNode1
(ReSharper | Inspect | Incoming Calls). Note how we’re going the other way here, in the direction opposite of method calls:
public interface IVisitor<T> { void VisitNode1(T data); } class Node1 { public void Accept<T>(IVisitor<T> v, T data) { v.VisitNode1(data); } } public class ConcreteVisitor1 : IVisitor<int> { public void VisitNode1(int data) { } } public class ConcreteVisitor2 : IVisitor<string> { public void VisitNode1(string data) { } } public class C1 { void Foo() { var v = new ConcreteVisitor1(); new Node1().Accept(v, 1); } void Foo2() { var v = new ConcreteVisitor2(); new Node1().Accept(v, "string"); } }
The result:
While traversing the generic Visitor
, ReSharper did not lose any details about the substituted type parameters and successfully filtered out the irrelevant call from Foo2
. When you have a highly-branched hierarchy and a large number of generic types, this kind of logic helps to really narrow down your search.
Constructors
Let’s also look at an artificial example using constructors and field initializers. Let’s search for outgoing calls from the Derived
class constructor:
class Base { public Base() { Base_Bar(); } void Base_Bar() { } } class Derived : Base { int _i = Foo(); public Derived() { Bar(); } void Bar() { } static int Foo() { return 0; } }
Again, nothing out of the ordinary. ReSharper simply displays calls in their the natural order, mindfully listing the implicit call of the base constructor. For a less-experienced developer, this saves a lot of time spent understanding code, and is a nice crutch to lean on for an expert.
Value Tracking
And here’s the final tidbit. If you open the ReSharper | Inspect menu, you’ll see two very interesting items: Value Origin and Value Destination. These functions implement value tracking: they let you track where a particular variable value or parameter value came from, or where it is headed. Naturally, it works with collections and delegates (it determines that an item was taken from a collection and then searches for usages of that particular collection) and is indispensable for identifying the causes of NullReferenceException
s.
Illustrating this will take a whole batch of screenshots and examples, so please stay tuned for our next post.
Author: Alexander Zverev, senior ReSharper developer. Translated from original article (in Russian)