В ReSharper 5.0 появилась новая функция Call Hierarchy. В сущности, она представляет собой удобный UI для массовых Find Usages или Go To Declaration.
Первоначально в статье я хотел сделать сравнительный анализ этой фичи в R# и в VS 2010, но в процессе написания обнаружилось, что Call Hierarchy в VS 2010 не выдерживает никакой критики (не работает с events, интерфейсами, замыканиями и проч.) и на примерах из статьи вообще не показывает ничего полезного и разумного. Поэтому я просто расскажу об интересных штуках, которые умеет Call Hierarchy в R#.
Попробуем искать исходящие вызовы из метода

Результат очевиден и понятен. R# благополучно находит все подписки на
Рассмотрим простой пример кода:
Теперь поищем исходящие вызовы из

Все очевидно.
Теперь добавим такой класс

Вызова
Теперь чуть более сложный, но еще более жизненный пример с паттерном Visitor. Найдем входящие (Incoming) вызовы метода
Результат будет таким:

Проходя через генерический визитор, R# не растерял информацию о подстановках параметров типа и смог отфильтровать лишний вызов из метода
Теперь несколько искусственный пример с конструкторами и инициализаторами полей. Ищем исходящие вызовы из конструктора класса

Опять же ничего необычного. R# просто отображает натуральный порядок вызовов, не забывая про неявный вызов базового конструктора. Это позволит неопытному разработчику сэкономить массу времени на понимание кода, и будет полезным для усталого опытного.
И на закуску. Если вы посмотрите в меню R# -> Inpect, то обнаружите два очень интересных пункта “Value Origin” и “Value Destination”. Эти две функции реализуют Data Flow Analysys, т.е. позволяют отследить откуда пришло или куда уйдет значение переменной или параметра. Естественно, DFA работает с коллекциями и делегатами (отслеживает, что элемент был взят из коллекции и переходит к поиску обращений к коллекции) и совершенно незаменим при поиске причин
Первоначально в статье я хотел сделать сравнительный анализ этой фичи в R# и в VS 2010, но в процессе написания обнаружилось, что Call Hierarchy в VS 2010 не выдерживает никакой критики (не работает с events, интерфейсами, замыканиями и проч.) и на примерах из статьи вообще не показывает ничего полезного и разумного. Поэтому я просто расскажу об интересных штуках, которые умеет Call Hierarchy в R#.
Events
Попробуем искать исходящие вызовы из метода
Foo
(R# -> Inspect -> Outgoing):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)
{
}
}
* This source code was highlighted with Source Code Highlighter.

Результат очевиден и понятен. R# благополучно находит все подписки на
event E
и показывает их как возможные варианты вызовов. Ничего сверхестественного, но очень удобно.Generics
Рассмотрим простой пример кода:
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)
{
}
}
* This source code was highlighted with Source Code Highlighter.
Теперь поищем исходящие вызовы из
Base.Foo
:
Все очевидно.
Теперь добавим такой класс
Main
и попробуем поискать исходящие вызовы из Foo
:class Main
{
void Foo()
{
Concrete2 c = null; // null, чтобы не загрязнять дерево вызовов
c.Do("string");
}
}
* This source code was highlighted with Source Code Highlighter.

Вызова
Concrete1.DoImplementation
больше нет! Все потому, что R# посмотрел на параметры типа и сделал вывод, что Base.Do
вызовется с T->string
(см. вторую строчку результатов: Base<string>.Do
— string
указывает на то, что мы вызываем метод с конкретной подстановкой), а соответственно отфильтровал Concrete1
, т.к. в нем используется наследование с подстановкой T->int
.Теперь чуть более сложный, но еще более жизненный пример с паттерном Visitor. Найдем входящие (Incoming) вызовы метода
ConcreteVisitor1.VisitNode1
(R# -> Inspect -> Incoming 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");
}
}
* This source code was highlighted with Source Code Highlighter.
Результат будет таким:

Проходя через генерический визитор, R# не растерял информацию о подстановках параметров типа и смог отфильтровать лишний вызов из метода
Foo2
. В случаях, когда у вас создана развесистая иерархия и большое количество генеричесикх типов, подобная логика позволяет кардинально сократить область поиска.Конструкторы
Теперь несколько искусственный пример с конструкторами и инициализаторами полей. Ищем исходящие вызовы из конструктора класса
Derived
: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;
}
}
* This source code was highlighted with Source Code Highlighter.

Опять же ничего необычного. R# просто отображает натуральный порядок вызовов, не забывая про неявный вызов базового конструктора. Это позволит неопытному разработчику сэкономить массу времени на понимание кода, и будет полезным для усталого опытного.
Value Tracking. Вместо заключения.
И на закуску. Если вы посмотрите в меню R# -> Inpect, то обнаружите два очень интересных пункта “Value Origin” и “Value Destination”. Эти две функции реализуют Data Flow Analysys, т.е. позволяют отследить откуда пришло или куда уйдет значение переменной или параметра. Естественно, DFA работает с коллекциями и делегатами (отслеживает, что элемент был взят из коллекции и переходит к поиску обращений к коллекции) и совершенно незаменим при поиске причин
NullReferenceException
. К сожалению, это потребует от меня еще целой пачки скриншотов и примеров, поэтому подробнее я напишу об этом в следующей статье, а вы пока можете самостоятельно попробовать DFA и рассказать о том, что вам понравилось, а что нет.