Основы программирования на C#

         

Отладочная печать и условная компиляция


Одним из основных средств отладки является отладочная печать, позволяющая получить данные о ходе и состоянии процесса вычислений. Обычно разрабатываются специальные отладочные методы, вызываемые в критических точках программы - на входе и выходе программных модулей, на входе и выходе циклов и так далее. Искусство отладки в том и состоит, чтобы получить нужную информацию о прячущихся ошибках, проявляющихся, возможно, только в редких ситуациях.

Хотелось бы иметь легкий механизм управления отладочными методами, позволяющий включать при необходимости те или иные инструменты. Для этого можно воспользоваться механизмом условной компиляции, встроенным в язык C#. Этот механизм состоит из двух частей. К проекту, точнее, к конфигурации проекта можно добавить специальные константы условной компиляции. Вызов отладочного метода может быть сделан условным. Если соответствующая константа компиляции определена, то происходит компиляция вызова метода и он будет вызываться при выполнении проекта. Если же константа не определена (выключена), то вызов метода даже не будет компилироваться и никаких динамических проверок - вызывать метод или нет - делаться не будет.

Как задавать константы компиляции? Напомню, что проекты в Visual Studio существуют в нескольких конфигурациях. В ходе работы с проектом можно легко переключаться с одной конфигурации на другую, после чего она становится активной, можно изменять настройки конфигурации, можно создать собственные конфигурации проекта. По умолчанию проект создается в двух конфигурациях - Debug и Release, первая из которых предназначена для отладки, вторая - для окончательных вычислений. Первая не предполагает оптимизации и в ней определены две константы условной компиляции - DEBUG и TRACE, во второй - определена только константа TRACE. Отладочная версия может содержать вызовы, зависящие от константы DEBUG, которые будут отсутствовать в финальной версии. Используя страницу свойств, к конфигурации проекта можно добавлять новые константы компиляции.

В лекции 2 рассказывалось, как добраться до страницы свойств проекта. Взгляните еще раз на рис. 2.3 этой лекции, где показана страница свойств, и обратите внимание на первую строчку, содержащую список констант условной компиляции активной конфигурации (в данном случае - Debug). К этому списку можно добавлять собственные константы.

<

Можно также задавать константы условной компиляции в начале модуля проекта вперемешку с предложениями using. Предложение define позволяет определить новую константу:

#define COMPLEX

Как используются константы условной компиляции? В языке С++, где имеется подобный механизм, определен специальный препроцессорный IF-оператор, анализирующий, задана константа или нет. В языке C# используется вместо этого гораздо более мощный механизм. Как известно, методы C# обладают набором атрибутов, придающих методу разные свойства. Среди встроенных атрибутов языка есть атрибут Conditional, аргументом которого является строка, задающая имя константы:

[Conditional ("COMPLEX")] public void ComplexMethod () {...}

Если константа условной компиляции COMPLEX определена для активной конфигурации проекта, то произойдет компиляция вызова метода ComplexMethod, когда он встретится в тексте программы. Если же такая константа отсутствует в конфигурации, то вызов метода игнорируется.

На методы, для которых возможно задание атрибута Conditional, накладывается ряд ограничений. Метод не должен быть:

  • функцией, возвращающей значение;
  • методом интерфейса;
  • методом с модификатором override. Возможно его задание для virtual-метода. В этом случае атрибут наследуется методами потомков.


Атрибут Conditional, обычно с аргументом DEBUG, сопровождает модули, написанные для целей отладки. Но использование этого атрибута не ограничивается интересами отладки. Зачастую проект может использоваться в нескольких вариантах, например, в облегченном и более сложном. Методы, вызываемые в сложных ситуациях, например, ComplexMethod, имеющий атрибут условной компиляции, будут вызываться только в той конфигурации, где определена константа COMPLEX.

Приведу пример работы с отладочными методами. Рассмотрим класс, в котором определены три метода, используемые при отладке:

public class DebugPrint { [Conditional("DEBUG")] static public void PrintEntry(string name) { Console.WriteLine("Начал работать метод " + name); } [Conditional("DEBUG")] static public void PrintExit(string name) { Console.WriteLine("Закончил работать метод " + name); } [Conditional("DEBUG")] static public void PrintObject(object obj, string name) { Console.WriteLine("Объект {0}: {1}", name, obj.ToString()); } }



В классе Testing определено поле класса:

int state = 1;

и группа методов:

public void TestDebugPrint() { DebugPrint.PrintEntry("Testing.TestDebugPrint"); PubMethod(); DebugPrint.PrintObject(state, "Testing.state"); DebugPrint.PrintExit("Testing.TestDebugPrint"); } void InMethod1() { DebugPrint.PrintEntry("InMethod1"); // body DebugPrint.PrintExit("InMethod1"); } void InMethod2() { DebugPrint.PrintEntry("InMethod2"); // body DebugPrint.PrintExit("InMethod2"); } public void PubMethod() { DebugPrint.PrintEntry("PubMethod"); InMethod1(); state++; InMethod2(); DebugPrint.PrintExit("PubMethod"); }

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


Рис. 23.1.  Трассировка вычислений в процессе отладки

При переходе к конфигурации Release отладочная информация появляться не будет.


Содержание раздела