Добавить в цитаты Настройки чтения

Страница 342 из 371

[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.PrintObj ect(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 отладочная информация появляться не будет.

Классы Debug и Trace

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

Классы Debug и Trace — это классы-двойники. Оба они находятся в пространстве имен Diagnostics, имеют идентичный набор статических свойств и методов с идентичной семантикой. В чем же разница? Методы класса Debug имеют атрибут условной компиляции с константой DEBUG, действуют только в Debug-конфигурации проекта и игнорируются в Release-конфигурации. Методы класса Trace включают два атрибута Conditional с константами DEBUG и TRACE и действуют в обеих конфигурациях.

Одна из основных групп методов этих классов — методы печати данных: Write, WriteIF, WriteLine, WriteLineIF. Методы перегружены, в простейшем случае позволяют выводить некоторое сообщение. Методы со словом If могут сделать печать условной, задавая условие печати в качестве первого аргумента метода, что иногда крайне полезно. Методы со словом Line дают возможность дополнять сообщение символом перехода на новую строку.

По умолчанию методы обоих классов направляют вывод в окно Output. Однако это не всегда целесообразно, особенно для Release-конфигурации. Замечательным свойством методов классов Debug и Trace является то, что они могут иметь много "слушателей", направляя вывод каждому из них. Свойство Listeners этих классов возвращает разделяемую обоими классами коллекцию слушателей — TraceListenerCollection. Как и всякая коллекция, она имеет ряд методов для добавления новых слушателей: Add, AddRange, Insert — и возможность удаления слушателей: Clear, Remove, RemoveAt и другие методы. Объекты этой коллекции в качестве предка имеют абстрактный класс TraceListener. Библиотека FCL включает три неабстрактных потомка этого класса:

• DefauitTraceListener — слушатель этого класса, добавляется в коллекцию по умолчанию, направляет вывод, поступающий при вызове методов классов Debug и Trace, в окно Output;

• EventLogTraceListener — посылает сообщения в журнал событий Windows;

 TextWriterTraceListener — направляет сообщения объектам класса TextWriter или Stream; обычно один из объектов этого класса направляет вывод на консоль, другой — в файл.

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

Помимо свойства Listeners и методов печати, классы Debug и Trace имеют и другие важные методы и свойства:

• Assert и Fail, проверяющие корректность хода вычислений — о них мы поговорим особо;

• Flush — метод, отправляющий содержание буфера слушателю (в файл, на консоль и так далее). Следует помнить, что данные буферизуются, поэтому применение метода Flush зачастую необходимо, иначе метод может завершиться, а данные останутся в буфере;

• AutoFiush — булево свойство, указывающее, следует ли после каждой операции записи данные из буфера направлять в соответствующий канал. По умолчанию свойство выключено, и происходит только буферизация данных;

 Сlose — метод, опустошающий буфера и закрывающий всех слушателей, после чего им нельзя направлять сообщения.

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

Рассмотрим пример работы, в котором отладочная информация направляется в разные каналы — окно вывода, консоль, файл:

public void Optima()

{

    double х, у=1;

    х= у — 2*Math.Sin(у);

    FileStream f = new FileStreamCDebuginfo.txt",

        FileMode.Create, FileAccess.Write);

    TextWriterTraceListener writer1 =

        new TextWriterTraceListener(f);