10 февраля 2010 в 18:56

ReSharper: Call Hierarchy

.NET*
В 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 и рассказать о том, что вам понравилось, а что нет.
+17
138
5
planerist 15,5

Комментарии (15)

+2
emendz #
Использовал два года назад беты решарпера, потом перестал из за
1. Проблем с производительностью на средне-больших солюшенах (10-15 проектов по 30-50 файлов).
2. Невозможностью нормально работать с C++ проектами при активированном решарпере.

Как с этим обстоит дело сейчас?
+2
xeon #
2хядерник + 4 Гб памяти (рекомендуемые требования решарпера) = всё отлично, даже с solution wide analysis = on в случае 30 проектов в solution. Каждый проект 10-60 файлов.

На 2 Гб памяти немного тоскливо. Во время активной отладки/программинга тяжелых проектов, часа через 4, студия занимает около 1 Гбайта.

P.S.
ReSharper 4.5 System Requirements
* Processor: Pentium IV 1.5 Ghz (Intel Core 2 Duo 2GHz recommended)
* Memory: 1Gb (4Gb recommended)
+3
A1lfeG #
Сейчас сижу на последней версии 4й ветки.
C производительностью проблем они не решили :(
Мощная тачка загибается как только может. Хотя в 2010 студии не пробовал
0
Angelina_Joulie #
Работаю на 4.х
У меня в solution порядка 56 проектов на c# — проблем ни каких.
У РеШарпера проблемы только с WebSite Project (не путать с Web Application) а так почти всё ок.

Но, проблемы с производительностью отрицать не буду — есть.
+1
azeffin #
1) Стало сильно лучше, но все равно не очень. Возможно это еще остатки проблем одновременной работы решарпера и team explorer'а. (C2D 2.3 с 4мя гб памяти, win 2003). На семерке x64 и кваде стало приемлемо. Сейчас использую ночные билды 5го.
2) Коллеги говорят, что все плохо.
0
sha1dy #
4 ядерный ксенон с HT.
4 гига DDRII.

10 проектов (все либы, не веб) с 50-70 файлами в солюшене.

Тормозит просто пипец. Что 4.5, что ночные 5.

Что три года назад, что сейчас.
0
xeon #
Спасибо за информацию! Завтра же обновляюсь до 5.0.
0
Niraxoid #
Полезный обзор, спасибо.
+2
nZeus #
Обожаю Решерпер. Но в 5й версии F2 работает просто отвратительно — столько лишнего пытается переименовать :(
Но JetBrains хочется пожелать процветания! Ребята молодцы!
–2
planerist #
а что конкретно лишнее переименовывает? может он просто много находит в строковых литералах? расскажите, пожалуйста, подробнее
0
nZeus #
Есть энтитя User. У нее есть пропертя Type. Если я решусь ее переименовать, то выдастся окошко с более чем 150 местами, где еще есть упоминание об Туре (при чем совсем не связанных с моей пропертей):

*.aspx.cs: if (Session[«Type»] != null)
.dbml: <Table Name=«dbo.ActionType» Member=«ActionTypes»>
.dbml: <Type Name=«AlbumGroup»>
.dbml: <Column Name=«Title» Type=«System.String» DbType=«VarChar(50) NOT NULL» CanBeNull=«false» /> (в этой строчке 2 замены)

При чем убрать галочки со строк дбмл-ки дело непростое — или кликай по всем найденным вхождениям, или с шифтом да контролом выделять их. Нельзя отменить все галочки с dbml-файла…
–1
planerist #
а почему вы сразу не уберете галочку «Search in comments and literal strings»?

«При чем убрать галочки со строк дбмл-ки дело непростое — или кликай по всем найденным вхождениям, или с шифтом да контролом выделять их. Нельзя отменить все галочки с dbml-файла…» — а про это вообще непонятно. Там ведь дерево, если нажать space на узле, то снимаются все галочки с потомков.
0
nZeus #
Потому что часть галочек всё же нужна.
Сейчас я просто перестал использовать Ф2. Переименовываю ручками, и по Ctrl-Shift-B смотрю где что упало. Плохой выход, конечно, легко в aspx-страницах что-то проглядеть, но вот иначе никак… :(
0
nZeus #
А для dbml-файлов нет узла… :)
0
planerist #
О, вот это уже замечание по делу. Постараемся сделать группировку по расширению файлов уже в 5-ке. Спасибо за репорт :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.