Pull to refresh

ReSharper: Call Hierarchy

Reading time 4 min
Views 1K
В ReSharper 5.0 появилась новая функция Call Hierarchy. В сущности, она представляет собой удобный UI для массовых Find Usages или Go To Declaration.

Первоначально в статье я хотел сделать сравнительный анализ этой фичи в 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>.Dostring указывает на то, что мы вызываем метод с конкретной подстановкой), а соответственно отфильтровал 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 и рассказать о том, что вам понравилось, а что нет.
Tags:
Hubs:
+17
Comments 15
Comments Comments 15

Articles