Определение места ошибки в исходном файле Относительно недавно я писал про Singleton и там была у меня не решенная проблема с тем, что не выводилось точное место возникновения ошибки. Анализ ошибки проходил как надо, информация дополнительная выводилась, но это было мало. Привычный шаблон работы, когда двойной клик по тексту ошибки приводил к навигации на место ошибки не работал. Это безмерно огорчало. После плодотворного общения с программистами PostSharp, оказалось, что это была ошибка в последней сборке и новый релиз, начиная с 4.0.41, ее исправляет. Дальше я хочу отдельно обратить внимание на то, что надо делать и рассказать немного о том, как вообще можно получить эту информацию в C#: до версии 4.5, с версии 4.5 и старше. PostSharp Эта часть статьи в целом задумана как обновление и привлечение внимания к статье про шаблон Singleton. Я ее обновил, но тем не менее. Использование Архитектурного фреймворка из состава PostSharp рано или поздно приведет вас к тому, что надо будет уведомлять программистов об ошибках/недочетах. Лучше всего это делать с помощью специализированных классов, которые построены с учетом специфики работы PostSharp на этапе сборке проекта. К таким специализированным классам относятся: MessageLocation – статический класс, который позволяет узнать место ошибки (строка, файл) на основе служебной информации о составной части класса (MethodInfo, ConstructorInfo и так далее). Message – класс со статическими и динамическими методами, который помогает сформировать сообщение для отображения для конечного пользователя – программиста. Общий шаблон работы с этими классами выглядит так: var messageLocation = MessageLocation.Of(propertyInfo); var messageText = "my message"; var message = new Message(messageLocation, SeverityType.Error, "f001", messageText, "", "file", null); Message.Write(message); С помощью первой строки определяется местоположение кода, относительно которого будет выведено предупреждение для программиста. Аргументом является информация о части класса. Вторая строка опциональная, но часто там большой string.Format(), который формирует детальное сообщение с конкретными именами классов, свойств и других элементов. В третьей строке создается экземпляр сообщения программисту на основе местоположения анализируемого кода, типа предупреждения, некоторой информации о классе ошибок, строка сообщения. Далее параметры не существенны в большинстве сценариев. Последняя строка использует статический метод Write(), класса Message. Метод принимает только что созданный экземпляр класса Message, для того, чтобы отобразить его на панели Error List. Класс MessageLocation позволяет задать местоположение ошибки вручную с помощью метода Explicit(). Либо можно использовать свойство Unknown. Метод Of() имеет большое количество перегрузок, чтобы можно было указать на любое место класса. Например, если передать просто указание на тип класса, то в сообщении об ошибке будет указание на объявление класса – строка с ключевым словом class. Лучше всего это проиллюстрирует следующий скриншот: До версии .NET 4.0 включительно Далее рассмотрим более традиционные способы получения детальной информации о том в каком месте исходного кода получилась ошибка. Традиционные в том плане, что надо залогировать место ошибки безотносительно аспектов. Для этого используется конструкция try…catch. В блоке обработки ошибки необходимо с помощью класса StackTrace получить стек ошибки и далее с помощью вспомогательных методов извлечь все необходимые данные. Имена методов говорят сами за себя: GetFileName() GetMethodName() GetFileLineNumber() Общий блок кода выглядит так: try { // Do something } catch (Exception ex) { var trace = new StackTrace(ex, true); Console.WriteLine("File: " + trace.GetFrame(0).GetFileName()); Console.WriteLine("Method: " + trace.GetFrame(0).GetMethod()); Console.WriteLine("Line: " + trace.GetFrame(0).GetFileLineNumber()); } В результате получим все что мы хотели для логирования данных об ошибке в исходном коде. Результат на скриншоте ниже. Не могу обойти стороной вопрос оборачивания такого кода в аспект, чтобы было удобнее с ним работать и не писать такую «простыню» кода каждый раз, когда надо получить данные об ошибке в конкретном методе при детальном логировании. Однако код приводить не буду, так как это очень простой аспект. Примеров на логирование очень много и не составляет труда найти образец для подражания. Начиная с .NET 4.5 и до настоящего времени Начиная с версии .NET 4.5 разработчики языка значительно упростили жизнь разработчикам реализовав то же самое поведение с помощью «аспектов». Хотя конечно же сама реализация просто реализована в глубине фреймворка. Атрибуты просто говорят, какое значение над вставить в параметры метода, если не приходит явное значение. Однако требуется при объявлении указать значения по умолчанию. Вот эти атрибуты, их имена так же ясно описывают поведение: [CallerFilePath] [CallerMemberName] [CallerLineNumber] Применение атрибутов выглядит вот так: public class Foo { public void TraceMessage(string message, [CallerFilePath] string callingFilePath = "", [CallerMemberName] string callingMethod = "", [CallerLineNumber] int callingFileLineNumber = 0) { Console.WriteLine("File: " + callingFilePath); Console.WriteLine("Method: " + callingMethod); Console.WriteLine("Line: " + callingFileLineNumber); } } В клиентском коде так же используется конструкция try…catch. try { // Do something } catch (Exception ex) { new Foo().TraceMessage(ex.Message); } Как вы можете видеть из приведенного куска кода, это не rocket science и не выходит за рамки разумного. Естественно, что класс и метод надо объявлять сообразно вашему коду. Результат работы кода выглядит как на скриншоте ниже: В проектах, использующих .NET 4.5 и выше код для вывода такой информации стал проще и жизнь легче. Хотя необходимость оборачивать подозрительные куски кода все равно остается. Если вам пришла идея подписаться на событие UnhandledException класса AppDomain, то это не очень удачная идея. И вот почему: По сообщению в консоли видно, что метод TraceMessage показывает место, откуда был вызван метод. А это как раз и есть UnhandledException. Так что пришлось бы использовать старый метод с использованием стека. Надо будет взять на один фрейм выше, для того, чтобы получить нужные данные. Это уже можно рассматривать как домашнее задание. =) Статья на сайте Violet-Tape Hard’n’Heavy!