Изменение поведения отладчика в Visual Studio

Какое неудобство настигает нас при отладке приложения в Visual Studio? Для меня всегда было неудобно осуществлять навигацию по свойствам объекта, когда нужна какая-то информация. В этой статье я приведу набор атрибутов, которые позволяют упростить данный процесс.

Для того, чтобы упростить этот процесс можно использовать набор предопределенных атрибутов.

DebuggerDisplay

Позволяет задать строку для класса или свойства, которая будет отображаться в отладчике.

public class Person
{
     public string FirstName { get; set; }

     public string LastName { get; set; }

     public int Age { get; set; }
}

По умолчанию, при просмотре значения отладчик отобразит полное название класса:

Добавим атрибут DebuggerDisplay:

[DebuggerDisplay("{FirstName} {LastName}, {Age} years old")]
public class Person
{
     public string FirstName { get; set; }

     public string LastName { get; set; }

     public int Age { get; set; }
}

Тогда отображение в отладчике изменится следующим образом:

Аналогичным образом можно настроить отображение значений свойств:

[DebuggerDisplay("{FirstName} {LastName}, {Age} years old")]
public class Person
{
     public string FirstName { get; set; }

     public string LastName { get; set; }

     [DebuggerDisplay("{Age} years old")]
     public int Age { get; set; }
}

DebuggerBrowsable

Позволяет управлять поведением отладчика, при отображении свойства. В конструкторе должен передаваться параметр DebuggerBrowsableState, который принимает одно из следующих состояний:

  • Collapsed - свойство будет отображаться свернутым, для просмотра его содержимого его нужно развернуть. Это поведение по-умолчанию.
  • Never - свойство не будет отображаться в отладчике.
  • RootHidden - применимо для свойств, содержащих коллекции. Если задана эта опция, то само свойство будет скрыто, а вместо него будут отображены элементы массива.

На практике использование этого атрибута выглядит так:

public class Person
{
     public string FirstName { get; set; }

     public string LastName { get; set; }

     [DebuggerBrowsable(DebuggerBrowsableState.Never)]
     public int Age { get; set; }

     [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
     public string[] Phones { get; set; }
}

DebuggerTypeProxy

Этот способ позволяет более радикально изменить способ отображения информации об объекте.

Для того, чтобы использовать атрибут DebuggerTypeProxy, необходимо создать класс, принимающий в конструкторе отображаемый объект. Свойства этого класса и будут отображаться в отладчике вместо оригинального класса. При этом содержимое исходного класса будет отображаться в ветке Raw View.

[DebuggerTypeProxy(typeof(PersonVisualizer))]
public class Person
{
     public string FirstName { get; set; }

     public string LastName { get; set; }

     public int Age { get; set; }
}

public class PersonVisualizer
{
     private readonly Person _person;

     public PersonVisualizer(Person person)
     {
          _person = person;
     }

     public string FullName
     {
          get { return _person.FirstName + " " + _person.LastName; }
     }
}

DebuggerVisualizer

Если все предыдущие способы позволяли повлиять на отображение значения в окне самого отладчика, то этот способ позволяет определить собственный пользовательский интерфейс для отображения данных. Такой способ может быть полезен при отображении сложных типов.

Для того, чтобы воспользоваться этим способом, необходимо подключить к проекту сборку Microsoft.VisualStudio.DebuggerVisualizers.

Следующим шагом будет создание объекта, ответственного за отображение отладочной информации. Для этого нужно создать класс-наследник DialogDebuggerVisualizer. В этом классе следует переопределить метод Show, который принимает контекст для отображения окна, а также объект, позволяющий получить доступ к исходному объекту.

public class PersonVisualizer : DialogDebuggerVisualizer
{
     protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
     {

     }
}

Здесь есть одно ограничение, которое накладывается на исходный объект - он должен быть сериализуемый (помечен атрибутом Serializable), для того, чтобы его можно было переслать за рамки приложения. Внутри метода Show пишем код для создания пользовательского интерфейса и отображаем данные:

public class PersonVisualizer : DialogDebuggerVisualizer
{
     protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
     {
          Person data = (Person)objectProvider.GetObject();

          Form form = new Form();

          form.Controls.Add(new Label
          {
               Dock = DockStyle.Top,
               Text = data.FirstName
          });

          form.Controls.Add(new Label
          {
               Dock = DockStyle.Top,
               Text = data.LastName
          });

          form.Controls.Add(new Label
          {
               Dock = DockStyle.Top,
               Text = data.Age.ToString()
          });

          windowService.ShowDialog(form);
     }
}

Последнее - сообщаем компилятору о том, какой класс отладчик может использовать для отображения информации об объекте:

[DebuggerVisualizer(typeof(PersonVisualizer))]
[Serializable]
public class Person
{
     public string FirstName { get; set; }

     public string LastName { get; set; }

     public int Age { get; set; }
}

Теперь при запуске отладчика, напротив значения появится кнопка.

При нажатии на ней отобразится созданная форма.

DebuggerHidden и DebuggerStepThrough

Позволяет скрыть от отладчика код при выполнении пошаговой отладки.

Если вы установите точку останова в методе, помеченноматрибутом DebuggerHidden, то точка останова не сработает. Аналогичное поведение будет при использовании команды отладчика Step Into.

При использовании атрибута DebuggerStepThrough, точки останова будут срабатывать, но команда отладчика Step Into будет игнорировать метод.

public class Person
{
     public string FirstName { get; set; }

     public string LastName { get; set; }

     public int Age { get; set; }

     [DebuggerHidden]
     public void Print()
     {
     }

     [DebuggerStepThrough]
     public void Display()
     {
     }
}

Аналогично можно работать с get- и set-методами свойств.

Conditional

Этот атрибут не относится к отладчику, а скорее влияет на компиляцию кода, но также бывает полезным при отладке.

Этим атрибутом можно разметить метод и указать некоторое условие, аналогично тому, как это делается в директивах препроцессора. Если условие сработало, то указанный метод выполнится; если нет, то метод будет исключен при компиляции кода.

Например, следующий метод будет запускаться только в случае, если приложение построено в конфигурации Debug:

public class Person
{
     public string FirstName { get; set; }

     public string LastName { get; set; }

     public int Age { get; set; }

     [Conditional("DEBUG")]
     public void Print()
     {
          Console.WriteLine(LastName);
     }
}

Существуют ограничения, которые накладываются на методы, с которыми может использоваться указанный атрибут:

  • Метод должен возвращать void.
  • Метод не является переопределением метода базового класса.
  • Метод не должен являться методом реализуемого интерфейса.

Эти нехитрые инструменты позволят чувствовать себя более комфортно при отладке кода. Особенно полезно использовать данные приемы, если вы разрабатываете код, который будет повторно использоваться многократно.