C# для чайников
.pdf
/ / и з б е ж а т ь т ы с я ч п и с е м с у к а з а н и е м , ч т о м о я п р о г р а м м а
/ / н е е р а б о т а е т |
|
|
|
|
|
C o n s o l e . W r i t e L i n e ( " Э т а п р о г р а м м а |
|
н е р а б о т а е т ! " ) ; |
|||
S t u d e n t s i = n e w S t u d e n t ( " S t u d e n t 1 " , |
1 ) ; |
||||
S t u d e n t s 2 |
= n e w S t u d e n t ( " S t u d e n t 2 " , |
2 ) ; |
|||
/ / d i s p l a y |
t h e t w o s t u d e n t s |
|
|
|
|
C o n s o l e . W r i t e L i n e ( " S t u d e n t 1 |
= |
", |
s i . T o S t r i n g ( ) ) ; |
||
C o n s o l e . W r i t e L i n e ( " S t u d e n t 2 |
= |
" , |
s2.. T o S t r i n g ( ) ) ; |
||
/ / |
Т е п е р ь т р е б у е м о т к л а с с а S t u d e n t в ы в е с т и в с е х |
/ / |
с т у д е н т о в |
S t u d e n t . O u t p u t A l l S t u d e n t s ( ) ;
/ / О ж и д а е м п о д т в е р ж д е н и я п о л ь з о в а т е л я
C o n s o l e . W r i t e L i n e ( " Н а ж м и т е < E n t e r > д л я " +
" з а в е р ш е н и я п р о г р а м м ы . , . " ) ;
C o n s o l e . R e a d ( ) ;
}
}
p u b l i c c l a s s |
S t u d e n t |
|
|
{ s t a t i c |
A r r a y L i s t a l l S t u d e n t s |
= n e w A r r a y L i s t ( ) ; |
|
p r i v a t e |
s t r i n g s S t u d e n t N a m e ; |
|
|
p r i v a t e |
i n t |
n I D ; |
|
p u b l i c S t u d e n t ( s t r i n g s N a m e , |
i n t n I D |
||
s S t u d e n t N a m e = s N a m e ; n I D = n I D ;
a l l S t u d e n t s . A d d ( t h i s ) ;
/ / T o S t r i n g - в о з в р а щ а е т и м я и и д е н т и ф и к а т о р с т у д е н т а
p u b l i c |
o v e r r i d e |
s t r i n g T o S t r i n g ( ) |
s t r i n g s = S t r i n g . F o r m a t ( " { 0 } ( { l } ) " , |
||
|
|
s S t u d e n t N a m e , n I D ) ; |
r e t u r n s ; |
|
|
p u b l i c |
s t a t i c |
v o i d O u t p u t A l l S t u d e n t s ( ) |
I E n u m e r a t o r i t e r = a l l S t u d e n t s . G e t E n u m e r a t o r ( ) ; / / И с п о л ь з у ю ц и к л f o r в м е с т о о б ы ч н о г о w h i l e
f o r ( i t e r . R e s e t ( ) ; i t e r . C u r r e n t ! = n u l l ; i t e r . M o v e N e x t ( ) )
{
S t u d e n t C o n s o l e
s = ( S t u d e n t ) i t e r .
. W r i t e L i n e ( " S t u d e n t
C u r r e n t - |
|
= { 0 } " , |
s . T o S t r i n g ( ) ) ; |
}
}
После удаления из программы всех ошибок времени компиляции можно перехо дить к делу.
Несмотря на то что все ошибки изгнаны, остались еще два предупреждения (они по являются в окне Error List с пиктограммой в виде желтого треугольника). Предупрежде-
Глава 21. Использование интерфейса Visual Studio |
513 |
ния означают потенциальные проблемы, которые недостаточно серьезны, чтобы считать их ошибками. Однако это не значит, что их можно игнорировать. Хорошенько изучите их, поскольку они могут указать на ошибки в вашем коде. Хотя, конечно, расшифровка сообщений компилятора порой труднее, чем расшифровка египетских иероглифов.
Просто для интересующихся — на прилагаемом компакт-диске имеется де монстрационная программа V S D e b u g G e n e r i . e s , в которой вместо хране ния a l l S t u d e n t s в A r r a y L i s t используется L i s t < T > , метод O u t p u - t A H S t u d e n t s () оказывается гораздо проще — и все это работает!
Пошаговая отладка
При обнаружении наличия ошибки в программе одним из наилучших первых шагов в ее локализации и устранении является возможность отладчика, известная под названи ем пошагового выполнения (single stepping). Для этого воспользуйтесь командой меню
D e b u g ^ S t e p O v e r или клавишей <F10>.
Даже если вы не очень хорошо запоминаете функциональные клавиши, в этом случае следует сделать исключение. Использовать меню Debug для доступа
к пошаговому выполнению Step O v e r и Step Into — непозволительно медлен
но (панель инструментов лучше меню, но и ей далеко до клавиатуры).
Нажатие <F10> приведет к выполнению демонстрационной программы V S D e b u g до открывающей фигурной скобки функции M a i n ( ) . Повторное нажатие <F10> выполнит функцию M a i n () до первой выполнимой инструкции (executable statement) — инструк ции, которая что-то делает на самом деле. Комментарии и объявления такими инструк циями не являются, а вот C o n s o l e . W r i t e L i n e () определенно осуществляет некото р ы е действия. На рис. 21.15 изображено окно Visual Studio после двукратного пошагово го выполнения.
Puc. 21.15. Пошаговый режим приводит к выполнению программы по одной инструкции
514 |
Часть VII. Дополнительные главы |
Перед началом процесса отладки Visual Studio перекомпилирует программу, так что не нервничайте преждевременно.
Обратите внимание, что первая строка программы подцвечена. Это — очередная ин струкция, которая будет выполнена в пошаговом режиме. Запомните — желтая строка
еще не выполнена.
Обратите также внимание на окно, открытое внизу, с именем Locals (оно может на ходиться в минимизированном состоянии; на рис. 21.15 оно показано открытым).
В этом окне выводится список трех локальных переменных, т.е. переменных, объяв ленных в текущей функции. Переменные si и s2 типа V S D e b u g . S t u d e n t имеют зна чения n u l l , поскольку им еще не были присвоены значения. Столбец Туре предостав
ляет информацию о |
полном имени класса, включая пространство имен. Переменная |
a r g s типа s t r i n g [] |
(массив строк) с длиной 0 означает, что программе не были пере |
даны аргументы. Это тоже очень важная возможность отладчика. |
|
Еще одно нажатие <F10> приводит к выполнению вывода предупреждения функцией W r i t e L i n e ( ) . Чтобы убедиться в этом, нажмите комбинацию клавиш <Alt+Tab> для того, чтобы переключиться на программу V S D e b u g . В консольном окне вы увидите одну строку — Э т а п р о г р а м м а не р а б о т а е т !. Это именно то, что и ожидалось. Еще раз нажмите <Alt+Tab> для возврата в Visual Studio.
<Alt+Tab>— команда переключения между программами Windows, исполь зуемая для передачи управления от одной программы к другой. Она "активизирует" главное окно программы, в которую вы переключаетесь. Когда активен отладчик, программа V S D e b u g выполняется, но находится в приоста новленном состоянии. Клавиши <Alt+Tab> можно использовать где угодно, а не только в Visual Studio.
Естественно, очередное нажатие <F10> приводит |
к |
выполнению |
строки |
S t u |
||
d e n t si |
= .... Поток управления останавливается на |
следующей строке функции |
||||
M a i n ( ) , |
выполняя конструктор первого объекта S t u d e n t за один шаг. Конструктор |
|||||
всегда выполняется, даже если вы этого и не видите. |
|
|
|
|
||
В окне |
Locals переменная |
s2 остается равной n u l l , |
но переменная |
s i теперь содер |
||
жит объект класса S t u d e n t . |
Небольшой знак "плюс" |
слева от si означает, что |
объект |
|||
можно открыть и посмотреть на его "внутренности". После щелчка на этом значке окно Locals приобрело вид, показанный на рис. 21.16, раскрывая содержимое объекта s i .
Первая и вторая записи в экземпляре объекта — члены n I D типа i n t и s S t u d e n t - N a m e типа s t r i n g . Третья запись — заголовок списка статических членов класса— в данном случае это единственный член данных a l l S t u d e n t s типа A r r a y L i s t . По скольку данный объект также имеет свое содержимое, слева от него находится малень кий значок "плюс", позволяющий ознакомиться с этим содержимым.
Взгляните внимательнее на значения двух членов экземпляра: s S t u d e n t N a m e имеет значение " S t u d e n t 1 " , которое выглядит вполне корректно, a n I D имеет значение О, что корректным не назовешь. Это значение должно быть равно 1 — значению, передан ному конструктору. Что-то в конструкторе пошло не так...
Беспокоясь о программировании на С# вообще и о своей карьере в частности, я вы бираю команду меню Debug<=>Stop Debugging. Затем я нажимаю <F10> три раза: один раз для перезапуска программы, и два раза для того, чтобы пройти W r i t e L i n e () —
Глава 21. Использование интерфейса Visual Studio |
515 |
тут, похоже, все нормально работает. Но вместо того чтобы нажать <F10> еще раз и вы полнить конструктор, не заходя в него, я нажимаю <F11> и вхожу в конструктор.
Рис, 21.16. Окно Locals позволяет получить детальную информацию о со стоянии объекта
Действие клавиш <F10> и <F11> идентично, когда вы выполняете инструкцию, не являющуюся вызовом функции некоторого вида. Пошаговое выполнение по средством <F11> (step into) приводит к пошаговому выполнению вызываемой функции. Однако оба пошаговых режима не позволяют заходить в библиотечные функции .NET. Исходный текст реализации библиотеки закрыт для входа.
В этот раз в окне появляется конструктор с выделенной первой строкой. Далее следу ет открыть интересующий объект t h i s в окне Locals. Затем несколько раз нажать <F10>, чтобы перейти к точке инициализации n I D .
Каждое изменяемое значение в окне Locals выделяется красным цветом.
Весь в ожидании, я нажимаю <F10> еще раз. Интересно— значение t h i s . n I D не изменяется, несмотря на то что значение n I D в окне Locals стало равным 1.
Если вы скомпилируете демонстрационную программу V S D e b u g с использо ванием команды D e b u g ^ B u i l d V S D e b u g вместо применения клавиши <F10> для пошагового прохода в отладчике, в окне Error List вы увидите два упомя нутых предупреждения. Если вы посмотрите на конструктор класса S t u d e n t , то увидите волнистую пурпурную линию под присваиванием n I D = n I D , ука зывающую на проблему. Если бы вы сделали это перед тем, как переходить к отладке, возможно, вы бы смогли исправить ошибку, не прибегая к отладчи ку. К сожалению, эта линия не видна на рис. 21.16, так как на нем показан код в отладчике.
516 |
Часть VII. Дополнительные главы |
Вернемся к анализируемой строке. Следующее выражение просто присваивает значе ние n I D самому себе:
n I D = n I D ;
Это законно, но бесполезно и совсем не то, что требовалось. Вот что было нужно на самом деле:
t h i s . n I D = n I D ; / / П р и с в а и в а н и е а р г у м е н т а п е р е м е н н о й - ч л е н у
Можно прекратить отладку и перекомпилировать программу, после чего, по местив курсор над этим присваиванием, посмотреть еще раз на предупрежде ние во всплывающем окне. Оно гласит "Assignment made to same variable; did you mean to assign something else?" ("Выполняется присваивание той же пере менной. Не намеревались ли вы выполнить иное присваивание?"). Можно ли выразиться понятнее?
Можно попробовать изменить строку на t h i s . n I D = n I D и снова пошагово вы полнить программу. (С некоторыми ограничениями вы можете также просто изменить исходный текст и продолжить отладку — эта возможность Visual Studio называется Edit and Continue — поищите информацию о ней в справочной системе.)
В этот раз следует аккуратно проверить объекты si и s2 в окне Locals после их кон струирования, но, кажется, все выглядит хорошо. Однако пошаговое выполнение оче редного вызова W r i t e L i n e () дает странный вывод на экран:
Э т а п р о г р а м м а н е р а б о т а е т ! S t u d e n t 1 =
Что могло случиться на этот раз? Вероятно, T o S t r i n g () ничего не возвращает. Не обходимо начать сначала, так что пока что я прекращаю отладку.
Главное - вовремя остановиться
Вероятно, вы уже замаялись в очередной раз пошагово проходить программу. Поша говый проход большой программы представляется вообще сплошным кошмаром.
Отладчик Visual Studio позволяет указать, что вы хотите остановить программу в ее конкретной точке. Это достигается путем создания так называемой точки останова (breakpoint).
Для этого нужно щелкнуть мышью на области слева от интересующей команды W r i t e L i n e ( ) , где предполагается приостановить выполнение программы. Возле стро ки появляется маленький красный кружок, а сама строка подцвечивается красным цве том, что свидетельствует о наличии точки останова. Теперь можно указать программе, что она может начинать выполнение, посредством команды меню D e b u g ^ S t a r t или кла виши <F5>. В результате ни о чем не нужно беспокоиться, зная, что программа остано вится, дойдя до указанной строки.
Как и должно быть, программа начинает работу и останавливается в заданной точке, как показано на рис. 21.17. Обратите внимание на желтую стрелку, появляющуюся в красном кружке, и на подцвечивание инструкции W r i t e L i n e () желтым цветом.
Далее следует вновь три раза нажать <F10>, чтобы добраться до инструкции W r i t e L i n e ( ) , которая выводит информацию о студенте 1, и затем — <F11>, чтобы попасть в метод T o S t r i n g ( ) .
Глава 21. Использование интерфейса Visual Studio |
517 |
Рис. 21.17. Желтая стречка указывает, где остановилась программа из-за наличия точки останова
Я не должен был передавать результат вызова S t r i n g . F o r m a t О оператору re t u r n , как показано в следующей строке:
r e t u r n S t r i n g . F o r m a t ( " { 0 } ( { l } ) " , s S t u d e n t N a m e , n I D ) ;
Вместо этого следовало бы переписать T o S t r i n g () с использованием временной переменной s:
p u b l i c o v e r r i d e s t r i n g T o S t r i n g ( )
{
s t r i n g s = S t r i n g . F o r m a t ( " { 0 } ( { l } ) " , s S t u d e n t N a m e , n I D ) ; r e t u r n s ;
}
Присваивание возвращаемого значения промежуточной переменной дает воз можность просмотреть его в отладчике. (Помимо этого, нет никаких иных при чин поступать таким образом, так что после отладки можно удалить эту про межуточную переменную.)
Нажмите < F U > для пошагового выполнения строки, вычисляющей значение s — строки, возвращаемой функцией T o S t r i n g ( ) . В окне Locals все выглядит вполне кор ректно, как видно из рис. 21.18. (Примечание: \ t , которое вы видите в строке з, пред ставляет собой символ табуляции. Я нажал <ТаЬ> вместо пробела, когда вводил строку S t r i n g . F o r m a t . На самом деле в этом нет ничего страшного. Вне отладчика выберите команду меню E d i t ^ A d v a n c e d ^ S h o w White Space для того, чтобы вместо пробелов выводилась точка, а вместо символов табуляции — стрелочка. Отключить этот режим можно аналогичным способом.)
Неприятность найдена
Проблема должна заключаться в самом вызове W r i t e L i n e ( ) . Следует дважды на жать <F10>, чтобы вернуться к этой строке. Ага! Управляющий элемент { о } , который
518 |
Часть VII. Дополнительные главы |
д о л ж ен выводить строку, возвращаемую T o S t r i n g ( ) , отсутствует. То есть в функцию передано значение для него, но сам элемент забыт. Чтобы исправить ошибку, две коман
д ы W r i t e L i n e ( ) |
надо переписать следующим образом: |
||
/ / d i s p l a y t h e |
t w o s t u d e n t s |
|
|
C o n s o l e . W r i t e L i n e ( " S t u d e n t |
1 = { o } " , |
s i . T o S t r i n g ( ) ) ; |
|
C o n s o l e . W r i t e L i n e ( " S t u d e n t |
2 = { o } " , |
s 2 . T o S t r i n g ( ) ) ; |
|
Рис. 21.18. Возвращаемое значение корректно. Так что же происходит?
Вероятно, ошибка произошла в |
результате перепутывания двух видов функций |
|||
W r i t e L i n e ( ) : |
|
|
|
|
/ / С и с п о л ь з о в а н и е м у п р а в л я ю щ е г о э л е м е н т а |
||||
C o n s o l e . W r i t e L i n e ( " S t u d e n t |
1 |
= |
" + |
s i . T o S t r i n g ( ) ) ' ; |
/ / С и с п о л ь з о в а н и е м у п р а в л я ю щ е г о э л е м е н т а |
||||
C o n s o l e . W r i t e L i n e ( " S t u d e n t |
1 |
= |
{ о } " , |
s i . T o S t r i n g ( ) ) ; |
Подсказка о данных
Сейчас самое время рассказать об одном очень ценном нововведении в отладчике Visual Studio 2005: подсказке о данных (DataTip). Такая подсказка представляет собой небольшой прямоугольник, который появляется, когда вы останавливаете курсор над пе ременной во время останова в отладчике. После того как я дважды нажал <F10> для воз врата в функцию M a i n ( ) , я помещаю курсор над переменной si внутри вызова W r i t e L i n e () (без щелчка) и вижу появившееся окошко с si и его значением T o S t r i n g ( ) : { S t u d e n t ID ( 1 ) }. Я игнорирую маленький квадратик, происхождение которого из-за введенного символа табуляции я пояснял ранее. Информацию о подсказке о данных можно получить из раздела "DataTip" справочной системы.
Подсказки работают только когда переменная находится "в контексте", т.е. ли бо в изучаемой в настоящий момент функции, либо является членом-данными текущего класса, и вы уже выполнили строку, в которой инициализируется эта переменная.
Глава 21. Использование интерфейса Visual Studio |
519 |
Теперь о существенном усовершенствовании подсказок, видном из рис. 21.19. По местите курсор мыши над знаком + в окошке s l . При этом откроется детальная инфор
мация об объекте s l . Если вы переместите курсор на значок |
+ перед S t a t i c |
m e m b e r s , а п о т о м — перед a l l S t u d e n t s , а з а т е м — перед [0] |
— нулевым членом |
A r r a y L i s t объекта a l l S t u d e n t s — то вы опять увидите sl — в этот раз уже внутри A r r a y L i s t объекта a l l S t u d e n t s .
Рис. 21.19. Подсказка о данных— отличное средство, чтобы быстро разо браться с содержимым объекта в отладчике
Подсказки позволяют погружаться все глубже и глубже в сложные объекты. (Ранее для получения этой информации необходимо было открывать окно Watch или QuickWatch для данной переменной, либо использовать окно Locals.)
Снова щелкните на красном кружке (см. рис. 21.17) для того, чтобы удалить точку останова. (Вы можете также воспользоваться командой меню D e b u g s
Delete All Breakpoints.) В меню Debug имеется еще одна команда D e b u g s
Windows1 ^Breakpoints, которая предоставляет доступ ко всем возможностям точек останова.
Стек вызовов
Далее я ставлю точку останова на вызове O u t p u t A l l S t u d e n t s ( ) , следующем не посредственно за двумя только что исправленными вызовами W r i t e L i n e ( ) . Я нажи маю <F5> для выполнения программы до этой точки и смотрю, что выведено в окне Console. Все выглядит как надо.
Затем я еще раз нажимаю <F10>, чтобы пропустить вызов O u t p u t A l l S t u d e n t s ( ) . И вот тут-то это и происходит.
Появляется сообщение об ошибке наподобие показанного на рис. 21.20. Оно привя зано к строке с циклом f o r , а именно к условию цикла, в котором вызывается свойство C u r r e n t итератора (об итераторах см. главу 20, "Работа с коллекциями"). Сообщение о б ошибке гласит: E n u m e r a t i o n h a s n o t s t a r t e d . C a l l M o v e N e x t (Перечисление не начато. Вызовите M o v e N e x t ) —- все, что следует знать о происшедшем.
520 |
Часть VII. Дополнительные главы |
Рис. 21.20. Visual Studio говорит о том, что забыт начальный вызов MoveNext ()
Я закрываю окно сообщения об ошибке и, чтобы получить немного дополнительной информации, командой меню D e b u g O W i n d o w s ^ C a l l Stack открываю окно стека вызо вов Call Stack, показанное на рис. 21.21 (здесь оно раскрыто для того, чтобы было луч ше видно представленную им информацию).
Puc; 21.21. Окно Call Stack полезно при поиске источника фатальной |
ошибки |
и для определения вашего местоположения |
|
Глава 21. Использование интерфейса Visual Studio |
521 |
Здесь содержится вся информация, которую может предоставить отладчик — и, как правило, ее достаточно много. Желтая стрелка и подцветка в окне редактора указывают на выражение, вызвавшее проблемы. Окно Call Stack описывает, как именно мы попали в точку генерации исключения: функция Main() вызвала OutputAllStudents () в строке 64. Информация о номерах строк в полосе состояния говорит, что строка 64 — это заголовок цикла for, который уже был указан окном с сообщением об ошибке.
Вы можете вывести номера строк в окне редактирования — для этого воспользуй тесь командой меню Tools^OptionsOEditor^C* и выберите Line Numbers.
В данном случае стек вызовов не оказывает особой помощи, так как включает только две функции: Main() и OutputAllStudents ( ) . Но в больших программах может быть очень сложно обнаружить, как именно вы попали в эти жернова — и стек вызовов окажет вам в таком случае неоценимую помощь.
Дополнительную информацию можно получить, щелкнув на ссылке View Detail в ок не сообщения об исключении и воспользовавшись полем InnerException (единственное со знаком + перед ним). Поместите курсор над StackTrace и получите дополнительную информацию. В верхней строке содержится фраза get_Current. Вот где настоящая неприятность — в вызове свойства Current итератора.
Беглый взгляд на документацию по свойству IEnumerator. Current проясняет, что не вызван метод MoveNext () перед попыткой получения первого элемента. Теперь, когда стало понятно, в чем дело, следует остановить отладчик щелчком на кнопке Stop Debugging в полосе инструментов Debug и вернуться в режим редактирования. (Кнопка панели инструментов — это ярлык команды меню Debug^Stop Debugging; аналогич ного эффекта можно добиться и с помощью клавиш <Shift+F5>.)
В этот момент у меня накапливается несколько симптомов, указывающих на свойство Current итератора. Исключение подцвечивает условие цикла for,
где вызывается Current, StackTrace в InnerException также упомина ет закулисное имя Current — get_Current. (Свойства в действительности реализуются за сценой с использованием методов с префиксами get_ или set_.) Итак, вопрос — что же такого могло начудить свойство Current? От вет — я не вызвал сначала MoveNext ( ) , так что свойство Current не полу чило начальное значение — первый элемент данных в ArrayList.
Дальнейшее исследование показывает, что весь цикл for — одна большая ошибка. Итератор завершает работу, когда MoveNext () возвращает false, а не когда Cur rent получает значение null. Обновленный цикл (теперь — более безопасный while) выглядит следующим образом:
public static void OutputAllStudents()
{
IEnumerator iter = allStudents.GetEnumerator(); while(iter.MoveNext()) // 'while1 , а не 'for'
{
Student s = (Student)iter.Current; Console.WriteLine("Student = {o}", s.ToString());
}
}
522 |
Часть VII. Дополнительные главы |
