Интернет-журнал "Домашняя лаборатория", 2007 №9 - Журнал «Домашняя лаборатория»
На методы, для которых возможно задание атрибута 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);
TextWriterTraceListener writer2 =
new TextWriterTraceListener(System.Console.Out);
Trace.Listeners.Add(writer1);
Debug.Listeners.Add (writer2);
Debug.WriteLine("Число слушателей: " +
Debug.Listeners.Count);
Debug.WriteLine("автоматический вывод из буфера: "+
Trace.AutoFiush);
Trace. WriteLinelf (x<0, "Trace: " + "x= " + x.ToString()
+ " у = " + y);
Debug. WriteLine (" Debug: " + "x= " + x.ToString() +
" у = " + у);
Trace.Flush();
f. Close();
}
В коллекцию слушателей вывода к слушателю по умолчанию добавляются еще два слушателя класса TextWriterTraceListener. Заметьте, что хотя они добавляются