ReSharper: Call Hierarchy

    В 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 и рассказать о том, что вам понравилось, а что нет.
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

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

      Как с этим обстоит дело сейчас?
      • +2
        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
          Сейчас сижу на последней версии 4й ветки.
          C производительностью проблем они не решили :(
          Мощная тачка загибается как только может. Хотя в 2010 студии не пробовал
          • 0
            Работаю на 4.х
            У меня в solution порядка 56 проектов на c# — проблем ни каких.
            У РеШарпера проблемы только с WebSite Project (не путать с Web Application) а так почти всё ок.

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

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

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

              Что три года назад, что сейчас.
            • 0
              Спасибо за информацию! Завтра же обновляюсь до 5.0.
              • 0
                Полезный обзор, спасибо.
                • +2
                  Обожаю Решерпер. Но в 5й версии F2 работает просто отвратительно — столько лишнего пытается переименовать :(
                  Но JetBrains хочется пожелать процветания! Ребята молодцы!
                  • –2
                    а что конкретно лишнее переименовывает? может он просто много находит в строковых литералах? расскажите, пожалуйста, подробнее
                    • 0
                      Есть энтитя 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
                        а почему вы сразу не уберете галочку «Search in comments and literal strings»?

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

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