|
||||||||||||||
|
Приложение CC# для разработчиков VB6 В этом приложении будет представлено краткое введение в язык C#, специально предназначенное для тех разработчиков, опыт которых до сих пор был частично или полностью связан с Visual Basic 6.
C# и Visual Basic являются очень разными языками как в своих синтаксических стилях, так и в фундаментальных концепциях, на которых они основываются. Это означает, что разработчикам Visual Basic потребуются существенные усилия, чтобы освоиться с C# даже на базовом уровне. С помощью этого приложения мы попытаемся облегчить процесс обучения, предоставив введение в C#, которое специально предполагает знание VB, и сосредоточившись на основных концептуальных различиях между двумя языками. В ходе этого приложения будет проведено сравнение, как кодировать решения проблемы в VB и в C#, представляя вместе код C# и код VB. Это означает, что рассмотрение языка C# ограничится базовым уровнем, здесь не будут рассмотрены более развитые свойства языка, им посвящены учебные главы основного текста книги. Мы сосредоточим внимание на различных методологиях, вовлеченных в написание кода с помощью языка C#. Различия между C# и VBПомимо очевидных синтаксических различий между языками, существуют две основные концепции, которые необходимо знать, чтобы можно было двигаться от VB к C#: 1. Концепция полного потока выполнения программы от начала до конца. Visual Basic скрывает этот аспект программ, так что в модулях классов кодируется только часть программы VB, связанная с обработкой событий и всеми методами. C# делает доступной всю программу в виде исходного кода. Это объясняется тем, что C# можно философски рассматривать как последующую генерацию C++, а корни C++ уходят в 60-е годы. C++ предшествовал оконным интерфейсам пользователя и развитым операционным системам. C++ развился как низкоуровневый, близкий к машине универсальный язык программирования. Написать приложение GUI с помощью C++ означает, что необходимо явно вызвать системные вызовы для создания и взаимодействия с оконными формами. Язык C# построили на этом наследии, упростив и модернизировав C++ так, чтобы низкоуровневые преимущества C++ в производительности можно было достичь с помощью кодирования не сложнее, чем в VB. VB, с другой стороны, является молодым языком, созданным специально для быстрой разработки приложений GUI Windows. По этой причине в VB весь стандартный код GUI является скрытым, и программист VB реализует только обработчики событий. C#, со своей стороны, показывает стандартный код как часть исходного кода. 2. Классы и наследование C# значительно более объектно-ориентированны, чем в VB, при условии, что весь код является частью класса. Он обеспечивает также исчерпывающую поддержку наследования реализации. На самом деле большинство хорошо спроектированных программ C# будут в значительной степени отвечать этой форме наследования, которая полностью отсутствует в VB. Основная часть этого приложения посвящена разработке двух примеров, для которых будут закодированы версии на VB и C#. Первый пример является простой формой, запрашивающей у пользователя число и выводящей квадратный корень и знак числа. Сравнивая подробно версии примера на VB и C#, мы увидим базовый синтаксис C# и поймем концепции, которые лежат в основе потока выполнения программы. Затем будет представлен модуль класса VB, который хранит информацию о сотрудниках, и его эквивалент в C#. Здесь мы начнем знакомиться с реальной мощью C#, так как при добавлении свойств в примеры быстро станет понятно, что VB просто не поддерживает концепции, которые нужны для разработки модуля класса согласно заданным требованиям, и придется продолжать только на C#. Приложение будет закончено кратким обзором некоторых из оставшихся различий между VB и C#, не показанных в примерах. Однако прежде чем начать, необходимо разобрать несколько концепций, касающихся классов, компиляции и базовых классов .NET. КлассыВ этом приложении будут достаточно интенсивно использоваться классы C#. Они представляют собой точно определенные объекты, которые подробно рассматриваются в главах 4 и 5. Но для логичности изложения лучше представлять их как эквиваленты C# модулей классов VB, так как они являются достаточно похожими сущностями. Подобно модулю класса VB, класс C# реализует свойства и методы и содержит переменные члены. Так же как для модуля класса VB, можно создавать объекты заданного класса C# (экземпляры класса) с помощью оператора new. За этим сходством, однако, скрывается много различии. Например, модуль класса VB является на самом деле классом COM. Классы C#, напротив, обычно не являются классами COM, но они всегда интегрированы в среду .NET. Классы C# являются также более динамичными чем их аналоги VB/COM, в том смысле, что они обеспечивают более высокую производительность и дают меньшую потерю быстродействия при создании экземпляра. Но эти различия мы почти не будем учитывать при обсуждении языка C#. КомпиляцияВы наверняка хорошо знаете, что компьютер никогда не выполняет напрямую код на любом языке высокого уровня, будь это VB, C++, С или любой другой язык. Вместо этого весь исходный код сначала транслируется в собственный исполнимый код машины с помощью процесса, обычно называемого компиляцией. При отладке VB предлагает возможность просто выполнить код сразу (то есть, когда каждая строка кода VB компилируется, или в этом случае говорят, что код интерпретируется, так как компьютер готов выполнить эту строку кода) либо произвести полную компиляцию (так, что вся программа сначала транслируется в исполнимый код, а затем начинается его реализация). Выполнение в начале полной компиляции означает, что все синтаксические ошибки обнаруживаются компилятором до того, как программа заработает. Это ведет к более высокой производительности и допускается только в C#. В C# компиляция делается в два этапа, где первый этап выполняется на так называемом промежуточном языке (IL) Этот этап будем называть собственно компиляцией. Второй этап — преобразование в исполнимый код, происходящее во время выполнения, является более простым этапом, поэтому он не ведет к значительным потерям производительности. Он также не является интерпретацией. Сразу целые части кода преобразуются из IL в язык ассемблера, и полученный исполнимый код на собственном языке машины затем сохраняется, поэтому не требуется его перекомпиляции в следующий раз, когда будет выполняться эта часть кода. Компания Microsoft считает, что в комбинации с различными оптимизациями это, в конечном счете ведет к коду, который действительно выполнится быстрее, чем при использовании предыдущей системы прямой компиляции из исходного кода в собственный исполнимый код. Хотя о существовании IL необходимо помнить, он не будет влиять на обсуждение в этом приложении, так как он реально не влияет на синтаксис языка C#. Базовые классы .NETVB не состоит просто из одного языка. Существует большое число связанных с ним функций, скажем, функции преобразования CInt, CStrи т.д., функции файловой системы, функции даты-времени и многие другие. VB также полагается на присутствие элементов управления ActiveX, предоставляющих стандартные элементы управления, которые помещаются на форме,— поля списков, кнопки, текстовые поля и т.д. C# также полагается на интенсивную поддержку из областей такого вида, но в случае C# поддержка предоставляется с помощью большого множества классов, известного как базовые классы .NET. Эти классы оказывают поддержку почти всем аспектам разработок под Windows. Существуют классы, представляющие все обычные элементы управления, дату, время, доступ к файловой системе, доступ в Интернет, а также многие другие. Здесь подробно не рассматривается библиотека базовых классов .NET, но она часто используется. На самом деле C# очень хорошо интегрирован в пазовые классы .NET, и можно обнаружить, что многие ключевые понятая C# — это просто оболочки определенных базовых классов. В частности, все базовые типы данных C#, которые используются для представления целых чисел, чисел с плавающей точкой, строк и т. д. являются на самом деле базовыми классами. Одним из важных различий между VB6 и C# в этом отношении является то, что система функций VB является специфической для VB, в то время как базовые класс .NЕТ доступны для любого поддерживающего .NET языка программирования. СоглашенияВ этом приложении код на C# часто сравнивается с Visual Basic. Чтобы облегчить идентификацию кода на двух языках программирования, код C# представлен в том же формате, что и в приложениях В и C: // Код который уже был показан // Код C#, на который необходимо обратить внимание или который // является новым Однако весь код VB дается в следующем формате: ' Код VB представлен на белом фоне Пример: Форма для извлечения квадратного корняВ этом разделе мы рассмотрим простое приложение, называемое SquareRoot, которое будет разработано на Visual Basic и на C#. Приложение является обычным диалоговым окном, которое приглашает пользователя ввести число и затем, когда пользователь нажимает кнопку, выводит знак и квадратный корень этого числа. Если число отрицательное, то квадратный корень необходимо вывести как комплексное число, что просто означает извлечение квадратного корня из обратного числа и добавление после него ' i'. Версия C# примера выглядит следующим образом. Версия VB по виду почти идентична, за исключением того, что имеет стандартною пиктограмму VB вместо пиктограммы оконных форм .NET в верхнем левом углу: Версия SquareRoot на VBЧтобы это приложение работало в Visual Basic, надо добавить обработчик событий для события нажатия кнопки. Для кнопки задается имя cmdShowResults, а текстовые поля имеют имена txtNumber, txtSignи txtResult. С этими именами обработчик событий выглядит следующим образом: Option Explicit Private Sub cmdShowResults_Click() Dim NumberInput As Single NumberInput = CSng(Me.txtNumber.Text) If (NumberInput < 0) Then Me.txtSign.Text = "Negative" Me.txtResult.Text = CStr(Sqr(-NumberInput)) & " i" ElseIf (NumberInput = 0) Then txtSign.Text = "Zero" txtResult.Text = "0" Else Me.txtSign.Text = "Positive" Me.txtResult.Text = CStr(Sqr(NumberInput)) End If End Sub Это единственный фрагмент кода VB, который необходимо написать. Версия SquareRoot на C#На C# также необходимо написать обработчик событий для события нажатия кнопки. Здесь сохраняются те же имена кнопки и текстовых полей, но на C# код выглядит следующим образом: // Обработчик событий нажатия пользователем кнопки Show Results. // выводится квадратный корень и знак числа private void OnClickShowResults(object sender, System.EventArgs e) { float NumberInput = float.Parse(this.txtNumber.Text); if (NumberInput < 0) { this.txtSign.Text = "Negative"; this.txtResult.Text = Math.Sqrt(-NumberInput).ToString() + " i"; } else if (NumberInput == 0) { txtSign.Text = "Zero"; txtResult.Text = "0"; } else { this.txtSign.Text = "Positive"; this.txtResult.Text = Math.Sqrt(NumberInput).ToString(); } } Сравнивая эти два примера кода, можно увидеть сходство в структуре кода и даже без всякого знания C# получить представление о том, что происходит. Также понятно, что существует множество различий в синтаксисе двух языков. Далее будет проведено сравнение этих примеров, чтобы детально обсудить синтаксис C#. В ходе этого процесса мы также выявим различия между базовыми методологиями C# и VB. Базовый синтаксисДавайте рассмотрим две программы SquareRoot для ознакомления с синтаксисом C#. С# требует, чтобы все переменные были объявленыНачнем с первой строки кода VB, где находится объявление Option Explicit. Эта инструкция не имеет аналога в C#, так как в C# переменные должны всегда быть объявлены до своего использования. Это соответствует тому, как если бы C# всегда выполнялся с включенным Option Explicitи не разрешал отключить этот режим. Поэтому нет необходимости явно объявлять Option Explicit. Причина такого ограничения заключается в том, что C# был очень тщательно спроектирован таким образом, чтобы затруднить случайное создание ошибок в коде. В VB рекомендуют всегда использовать Option Explicit, потому что это препятствует созданию трудно находимых ошибок, вызываемых неправильно записанными именами переменных. Легко заметить, что C# не позволяет делать вещи, которые с большой вероятностью могут привести к ошибкам. КомментарииКомментирование кода всегда важно, поэтому дальше в обоих примерах (или первое, что делается в примере на C#) добавляется комментарий: // Обработчик событий нажатия пользователем кнопки Show Results. // Выводится квадратный корень и знак числа private void OnClickShowResults(object sender, System.EventArgs e) { В VB для обозначения начала комментария используется апостроф, а комментарий продолжается до конца строки. Комментарии в C# в этом коде действуют таким же образом, за исключением того, что начинаются с двух прямых наклонных черт: //. Также как и для комментариев VB, можно использовать всю строку или добавить комментарий в конце строки: // Этот код определяет результаты int Result = 10 * Input; // получение результата Однако C# более гибок в своих комментариях, так как позволяет использовать два других способа указания комментариев, каждый из которых имеет слегка различный эффект. Комментарий также может быть ограничен последовательностями символов /* и */. Другими словами, если компилятор встречает последовательность /*, он предполагает, что весь последующий текст является комментарием, пока не встретит последовательность */. Это позволяет иметь длинные комментарии, которые распространяются на несколько строк: /* этот текст действительно является длинным длинным длинным длинным комментарием * / Короткие комментарии внутри строки являются очень полезными, если необходимо только временно заменить что-то в строке во время отладки: X = /* 20 */ 15; Третий способ похож на первый. Однако теперь используется три слэша: /// <summary> /// Event handler for user clicking Show Results button. /// Displays square root and sign of number /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnClickShowResults(object sender, System.EventArgs e) Если впереди используются три наклонные черты вместо двух, то комментарий по-прежнему продолжается до конца строки. Однако этот комментарий имеет теперь дополнительный результат: компилятор C# способен на самом деле использовать комментарии, которые начинаются с трех наклонных черт, чтобы автоматически создавать документацию для исходного кода как отдельный файл XML. Именно поэтому пример выше имеет достаточно формальную структуру для реального текста комментария. Эта структура готова к размещению в файле XML. Здесь не будут представлены детали этого процесса (он рассмотрен в главе 3). Необходимо только сказать, что комментируя каждый метод в коде, можно автоматически получить законченную документацию, которая обновляется при изменении кода. Компилятор будет даже проверять, что документация соответствует сигнатурам методов и т.д. Разделение и группировка инструкцийНаиболее заметным различием между приведенными выше кодами на VB и на C# будет, почти наверняка, присутствие точек с запятыми и фигурных скобок в коде C#. Хотя это делает код C# довольно устрашающим, принцип на самом деле очень простой. Visual Basic применяет возврат каретки для указания конца инструкции, в то время как в C# используется для той же цели точка с запятой. Фактически компилятор полностью игнорирует все лишние пробелы, включая возвраты каретки. Эти свойства синтаксиса C# можно комбинировать, чтобы предоставить большую свободу при размещении кода. Например, следующий код (переформатированный из части приведенного выше примера) также вполне допустим в C#: this.txtSign.Text = "Negative"; this.txtResult.Text = Math.Sqrt(-NumberInput) + " i"; Хотя очевидно, что если потребуется, чтобы другие люди смогли прочитать код, лучше предпочесть первый стиль кодирования. Visual Studio.NET будет в любом случае автоматически размещать код в таком стиле. Скобки используются для группирования вместе инструкций в так называемые блочные инструкции (или иногда составные инструкции). Эта концепция в действительности не существует в VB. Можно сгруппировать вместе любые инструкции, помещая вокруг них скобки. Группа теперь рассматривается как одна блочная инструкция и может использоваться в любом месте в C#, где ожидается одиночная инструкция. Блочные инструкции часто используются в C#. Например, в приведенном выше коде C# не существует явного указания на конец метода (C# имеет методы там, где VB имеет функции и подпрограммы). VB требуется инструкция End Subв конце любой подпрограммы, так как подпрограмма может содержать сколько угодно инструкций. Специальный маркер является единственным способом, который известен в VB для определения конца подпрограммы. C# действует по-другому. В C# метод формируется в точности из одной составной инструкции. В связи с этим он заканчивается закрывающей фигурной скобкой, соответствующей открывающей скобке в начале метода. Следующее действие часто встречается в C#: там, где Visual Basic использует некоторое ключевое слово для пометки конца блока кода, C# просто объединяет блок в составную инструкцию. Инструкция ifв приведенных выше примерах иллюстрирует такой подход. В VB необходима инструкция EndIfдля отметки окончания блока. В C# правило состоит в том, что предложение ifвсегда содержит точно одну инструкцию, и предложение elseтоже одну. Если необходимо поместить более одной инструкции в любом предложении, как в случае примера выше, используется составная инструкция. Использование заглавных буквЕще одна особенность, которую можно заметить в отношении синтаксиса, состоит в том, что все ключевые слова— if, else, intи т.д. в коде C# пишутся со строчной буквы. В отличие от VB язык C# различает заглавные и строчные буквы. Если написать Ifвместо if, то компилятор не поймет этот код. Одним из преимуществ использования заглавных и строчных букв является возможность существования двух переменных, имена которых различаются только регистром символов, таких как Nameи name. Такая ситуация встретится во втором примере этого приложения.
Как правило, ключевые слова C# записываются полностью строчными буквами. МетодыДавайте сравним синтаксис, применяющийся в VB и C# для объявления части кода, которая будет обрабатывать событие. В VB: Private Sub cmdShowResults_Click() а в C#: private void OnClickShowResults(object sender, System.EventArgs e) Первое замечание, которое необходимо сделать, состоит в том, что версия VB объявляет подпрограмму, в то время как версия C# объявляет метод. В Visual Basic код традиционно группируется в подпрограммы и функции с лежащей в основе этого концепцией процедуры. Помимо этого, объекты классов VB имеют так называемые методы, которые для всех практических целей означают то же самое, что и процедуры, за исключением того, что они являются частью модуля класса. C#, наоборот, обладает только методами (в соединении с тем фактом, что, как будет показано позднее, все в C# является частью класса). В C# нет отдельной концепции функции и подпрограммы — эти термины даже не существуют в спецификации этого языка. В VB единственное реальное различие между подпрограммой и функцией состоит в том, что подпрограмма никогда не возвращает значение. В C#, если метод не должен возвращать значение, то это объявляется как возвращение void(как выше проиллюстрировал метод OnClickShowResults()). Синтаксис объявления метода аналогичен в обоих языках, по крайней мере в том, что параметры следуют за именем метода в скобках. Отметим, однако, что в то время как в VB подпрограмма объявляется с помощью ключевого слова Sub, в версии C# соответствующего слова не существует. В C# возвращаемого типа ( voidв данном случае), за которым следует имя метода и открывающая скобка, будет достаточно для сообщения компилятору, что объявлен метод, так как ни одна другая конструкция в C# не имеет подобного синтаксиса (массивы в C# помечаются квадратными, а не круглыми скобками, поэтому нет риска смешения с ними). Подобно Sub в VB, приведенному выше объявлению метода в C# предшествует ключевое слово private. Оно имеет примерно то же значение, что и в VB — не позволяет внешнему коду видеть метод. Что точно понимается под "внешним кодом", будет рассмотрено позже. Необходимо отметить еще два различия в объявлении метода: версия C# получает два параметра и имеет имя, отличное от обработчика событий в VB. Сначала рассмотрим различие в имени. Имя обработчика событий в VB задается как VB IDE. VB знает, что Sub является обработчиком событий для нажатия кнопки, потому что используется имя cmdShowResults_Click. Если переименовать подпрограмму, то она не будет вызываться при нажатии кнопки. Однако C# не использует имя таким образом. В C#, как скоро будет показано, существует некий код, который сообщает компилятору, какой именно метод является обработчиком событий. Это означает, что обработчику можно задать любое имя. Однако что-нибудь начинающееся с Onдля обработчика событий является традиционным, в C# обычная практика именования методов (и в связи с этим большинства других элементов) с помощью так называемой системы имен в стиле Pascalкоторая означает, что слова соединяются вместе, а первая буква слова делается заглавной. Использование подчеркиваний в именах C# не рекомендуется, и имя здесь выбрано в соответствии с этими рекомендациями: OnClickShowResults(). Теперь о параметрах. В этом приложении не будут подробно рассматриваться параметры, но отметим просто, что все обработчики событий в C# должны иметь два параметра, аналогичные данным, и эти параметры могут предоставить некоторую дополнительную полезную информацию о событии (например, для события перемещения мыши они могут сообщать о положении указателя мыши). ПеременныеПример SquareRootпоказывает достаточно много различий между объявлениями переменных в C# и VB. В версии VB объявляется число с плавающей точкой и задается его значение таким образом: Dim NumberInput As Single NumberInput = CSng(Me.txtNumber.Text) Версия C# выглядит следующим образом: float NumberInput = float.Parse(this.txtNumber.Text); Как и можно было ожидать, типы данных в C# не совсем такие, как в VB. floatявляется эквивалентом в C# для Single. Наверно проще понять, что происходит, если разделить версию C# на две строки. Следующий код C# имеет точно такой же результат, что и строка выше: float NumberInput; NumberInput = float.Parse(this.txtNumber.Text); Теперь можно сравнить объявление и инициализацию переменной по отдельности. ОбъявленияОчевидное синтаксическое различие между C# и VB в объявлении переменных состоит в том, что в C# тип данных предшествует, а не следует за именем переменной без использования других ключевых слов. Это дает объявлениям C# более компактный формат, чем их аналогам в VB. Отметим, что идея объявления, состоящего только из типа, за которым следует имя, используется также и в других местах. Посмотрим снова на объявление метода в C#: private void OnClickShowResults(object sender, System.EventArgs e); Тип ( void) предшествует имени метода, не используя никаких других ключевых слов для указания того, что объявляется — это очевидно из контекста. То же самое справедливо для параметров. Типами параметров являются objectи System.EventArgs. Тип objectв C#, кстати, играет роль, аналогичную Objectв VB,— он указывает то, для чего тип данных не был определен. Однако objectв C# значительно более мощный, чем Object в VB, и в C# objectзаменяет тип данных Variant из VB. objectмы рассмотрим позднее. System.EventArgsне будет рассматриваться подробно в этом приложении. Это базовый класс .NET и он не имеет аналога в VB. В случае переменных синтаксис объявления, использованный в C#, позволяет комбинировать объявление с заданием начального значения переменной. В этом примере NumberInputинициализируется достаточно сложным выражением, которое скоро будет рассмотрено подробнее. Но сначала два простых примера: int x = 10; // int аналогично Long в VB string Message = "Hello World"; // string аналогично String в VB Необходимо также отметить некоторые моменты, связанные с переменными. Никаких суффиксов в C#VB позволяет присоединять суффиксы к переменным, чтобы указать их тип данных: $для String, %для Int, &для Long. Dim Message$ ' будет string Такой синтаксис не поддерживается в C#. Имена переменных могут содержать только буквы, цифры и символ подчеркивания, и необходимо всегда явно указывать тип данных. Никаких значений по умолчанию для локальных переменныхВ примере кода VB переменной NumberInputпо умолчанию будет присвоено значение 0 после ее объявления. Это на самом деле ненужная фата процессорного времени, так как этой переменной немедленно в следующей инструкции присваивается новое значение. C# немного больше знает о производительности и не беспокоится о задании каких-либо значений по умолчанию для локальных переменных при их объявлении. Вместо этого он требует, чтобы такие переменные всегда инициализировались в коде программы до их использования. Компилятор C# будет инициировать ошибку компиляции, если попытаться прочитать значение локальной переменной прежде, чем она будет задана. Присваивание значений переменнымПрисваивание значений переменным в C# делается с помощью такого же синтаксиса, как и в VB. После имени переменной помещается знак =, за которым следует присваиваемое значение. Однако необходимо отметить, что это единый синтаксис, принятый в C#. В некоторых случаях в VB используется Let, в то время как для объектов в VB всегда используется ключевое слово Set: Set MyListBox = new ListBox; C# не использует отдельный синтаксис для присваивания объектных ссылок. Эквивалент в C# для вышеприведенного будет следующим: MyListBox = new ListBox();
КлассыТеперь рассмотрим, что происходит в выражении, используемом для инициализации переменной NumberInputв примере SquareRoot. В C# и VB делается практически одно и то же: извлекается текст из текстового поля txtNumber. Но синтаксис этого выглядит по-разному в двух этих языках: NumberInput = CSng(Me.txtNumber.Text) и float NumberInput = float.Parse(this.txtNumber.Text); Получение значения из текстового поля достаточно похоже в обоих случаях. Единственное различие для этой части процесса является чисто синтаксическим — VB использует ключевое слово Me, в то время как C# применяет ключевое слово this, которое имеет точно такое же значение (фактически, в C# можно его при желании опустить, так же как можно опустить Meв VB). В C# можно было в равной степени написать: float NumberInput = float.Parse(txtNumber.Text); Более интересной частью является то, как строка, извлеченная из текстового поля, преобразуется во float(или single), потому что это иллюстрирует фундаментальное свойство языка C#, о котором кратко упоминалось ранее:
В VB для преобразования используется функция CSng. Однако C# не имеет функций в том виде, как в VB. C# является полностью объектно-ориентированным и разрешает объявлять только те методы, которые являются частью класса. В C# преобразование из строки в число с плавающей точкой выполняется методом Parse(). Однако, так как Parse()является частью класса, ему должно предшествовать имя класса. Класс, на котором необходимо вызвать метод Parse(), будет классом float. До сих пор floatинтерпретировался просто как эквивалент C# для Singleиз VB. Но на самом деле он также является классом. В C# все типы данных тоже являются классами, и значит, такие вещи как int, floatи stringимеют методы и свойства, которые можно вызывать (хотя необходимо отметить, что intи floatявляются специальными типами класса, известного в C# как структуры. Различие для этого кода не важно, но оно будет объяснено позже.)
Инструкции IfМы переходим к основной части обработчика событий — инструкции if. Вспомните, что версия VB выглядит следующим образом: If (NumberInput < 0) Then Me.txtSign.Texgt = "Negative" Me.txtResult.Text = CStr(Sqr(-NumberInput)) & " i" ElseIf (NumberInput = 0) Then txtSign.Text = "Zero" txtResult.Text = "0" Else Me.txtSign.Text = "Positive" Me.txtResult.Text = CStr(Sqr(NumberInput)) EndIf в то время как версия C# записывается так: if (NumberInput < 0) { this.txtSign.Text = "Negative"; this.txtResult.Text = Math.Sqrt(-NumberInput).ToString() + " i"; } else if (NumberInput == 0) { txtSign.Text = "Zero"; txtResult.Text = "0"; } else { this.txtSign.Text = "Positive"; this.txtResult.Text = Math.Sqrt(NumberInput).ToString(); } Фактически наибольшее синтаксическое различие здесь уже было объяснено: каждая часть инструкции в C# должна быть одиночной инструкцией, следовательно, если необходимо условно выполнить более одной инструкции, надо объединить их в одну блочную инструкцию. В C#, если существует только одна инструкция для условного выполнения, не нужно формировать блочную инструкцию. Например, если пропустить задание текста в текстовом поле txtSignв приведенном выше коде, то можно написать: if (NumberInput < 0) this.txtResult.Text = Math.Sqrt(-NumberInput) + " i"; else if (NumberInput == 0) txtSign.Text = "Zero"; else this.txtResult.Text = Math.Sqrt(NumberInput).ToString(); Существуют и другие различия в синтаксисе, которые необходимо отметить. В C# скобки вокруг условия, которое проверяется в инструкции if, является обязательным. В VB можно написать: If NumberInput < 0 Then Попытка выполнить то же самое в C# немедленно приведет к ошибке компиляции. В целом C# значительно более точен в отношении ожидаемого синтаксиса, чем VB. Отметим также, что при проверке равенства нулю NumberInputдля сравнения используются два последовательных знака равенства: else if (NumberInput == 0) В VB символ =применяется в двух назначениях: для присваивания значений переменным и для сравнения значений. C# формально распознает это как операции двух различных типов и поэтому использует два различных символа: =для присваивания и ==для сравнения. Существует еще одно важное различие, которое надо учитывать, так как оно может легко привести к ошибкам при переходе от VB к C#: else ifсостоит в C# из двух слов, а в VB используется одно словоElseIf Вычисление квадратного корня: еще один метод классаВ соответствии со сделанным ранее замечанием о том, что все в C# является членом класса, будет неудивительно узнать, что эквивалент в C# функции Sqrиз VB, которая вычисляет квадратный корень, также является методом, являющимся членом класса. В данном случае это метод Sqrt(), который представляют статический член другого базового класса .NET с именем System.Math, сокращаемый в коде просто до Math. Можно также заметить, что в этом примере в условии с введенным числом, точно равным нулю, ключевое слово thisв коде C# не определено: txtSign.Text = "Zero"; txtResult.Text = "0"; и в соответствующем коде VB также не определяется явно Me. В C# по аналогии с VB, не требуется явно определять this( Me), если только по какой-то причине контекст окажется не ясным. Здесь это делается только для иллюстрации. СтрокиПри выводе квадратного корня отрицательного числа выполняется небольшая работа со строками: this.txtResult.Text = Math.Sqrt(-NumberInput).ToString() + " i"; Этот код покалывает, что в C# конкатенация строк делается с помощью символа +, а не &. Можно также заметить, что преобразование из floatв Stringвыполняется с помощью вызова метода на объекте float. Метод называется ToString(), и он не является статическим, поэтому вызывается с помощью того же синтаксиса, что и в VB при вызове методов на объектах, способом задания перед именем метода имени переменной, представляющей объект, с точкой. В отношении C# необходимо помнить одну полезную вещь — каждый объект (и следовательно, каждая переменная) реализует метод ToString(). Дополнительный код в C#Мы завершили сравнение процедур обработки событий в C# и VB. В процессе обсуждения мы много узнали о синтаксических различиях между этими языками. Фактически была показана большая часть базового синтаксиса C#. Мы также впервые столкнулись с тем фактом, что все в C# является классом. Однако, если загрузить код нашего примера с web-сайта издательства Wrox, и просмотреть его, то почти наверняка можно будет заметить, что мы тщательно избегали какого-либо обсуждения наиболее очевидного различия между двумя примерами: в примере на C# в действительности код намного длиннее и включает не только обработчик событий. Для версии VB примера SquareRootкод обработчика событий, который здесь представлен, представляет весь исходный код этого проекта. Напротив, в версии C# этот обработчик событий является только одним методом в огромном файле исходного кода. Причина, по которой код в проекте C# такой большой, связана с тем, что Visual Basic IDE большая часть того, что делается в программе, исходит от программиста. В Visual Basic требовалось написать только обработчик событий, но фактически выполняется значительно больше: запускается пример, выводится форма на экране, посылается информация Windows, зависящая от того, что желательно делать с событиями, и по окончании пример завершается. В Visual Basic программист не имеет доступа к коду, который делает все. C#, напротив, использует совершенно другую философию, и оставляет весь этот код открытым. Это может сделать внешний вид исходного кода более сложным. Но имеется одно преимущество: если код доступен, то его можно редактировать и это обеспечивает значительно большую гибкость в решении того, как должно себя вести приложение. Фактически Visual Basic настолько успешно скрывает почти все, что происходит в программе, что очень легко стать профессиональным программистом и создавать достаточно сложные приложения, не имея на самом деле никакого представления о полной структуре компьютерной программы. В следующем разделе будет рассмотрено, что же происходит в любой такой программе, и таким образом мы будем готовы взглянуть на весь дополнительный код, который содержится в версии C# программы SquareRoot. Что происходит при выполнении программыЛюбая программа содержит точную последовательность выполнения. Когда приложение запускается, будет существовать определенное место в исполнимом коде, с которого, как знает компьютер, он должен начать выполнение кода, другими словами, инструкция, которая выполняется первой. Затем будет идти следующая инструкция, и следующая, и следующая и так далее. Некоторые из этих команд прикажут компьютеру перепрыгнуть к другой инструкции, возможно в зависимости от значений, которые содержатся в некоторых переменных. Очень часто компьютер будет перепрыгивать назад и выполнять ту же самую инструкцию снова. Однако всегда существует эта непрерывная последовательность выполнения следующей инструкции, пока компьютер не встретит команду, которая прикажет ему прекратить выполнение кода. Такая линейная последовательность справедлива для любой программы. Некоторые программы могут быть мультипоточными, и в этом случае существует несколько последовательностей выполнения (потоков выполнения), но каждый поток по-прежнему будет следовать от начальной инструкции до остановки программы. Конечно, эта последовательность не та, которую можно видеть при написании исполнимой программы VB. В VB6 по сути пишется набор обработчиков событий — набор подпрограмм, каждая из которых вызывается, когда пользователь что-то сделает. Не существует единственного начала программы, хотя обработчик события Form_Loadблизко подходит к этому по своей концепции. Даже в этом случае Form_Loadявляется на самом деле только еще одним обработчиком для события, которое возникает, когда загружается форма, и значит, оно будет первым выполняющимся событием. Аналогично, если вместо исполнимого кода пишется элемент управления или объект класса, то нет начальной точки. Просто задается класс и к нему добавляется множество методов и свойств. Каждый метод или свойство будет выполняться, если или когда код клиента его вызывает.
Чтобы увидеть, как можно связать две идеи программирования, давайте выясним, что реально происходит, когда выполняется любое приложение Visual Basic или любое приложение GUI Windows, не важно на каком языке оно написано. Это более ограниченное рассмотрение, чем в случае приложении, которые упоминались перед этим, так как теперь мы сосредоточимся только на приложениях Windows API (другими словами, нет консолей, служб и т.д.). Как обычно, выполнение начинается в некоторой вполне определенной точке. Команды будут вовлекать в работу некоторые окна и элементы управления, а также вывод этих элементов управления на экран. В этом месте программа делает затем что-то, что называют входом в цикл сообщений. На самом деле программа засыпает и приказывает Windows разбудить себя, когда произойдет что-то интересное, о чем ей необходимо знать. Эти "интересные" вещи являются событиями, для которых необходимо написать обработчики событий, а также существует достаточно много событий, для которых не требуется писать свои собственные обработчики событий, так как если обработчик для определенного события написан не будет, то VB IDE спокойно представляет готовый по умолчанию. Хорошим примером этого являются обработчики, которые имеют дело с изменением размера формы. Исходный код для него никогда не показывается в VB, но приложение VB может правильно отреагировать, когда пользователь попытается изменить размер, поскольку VB IDE незаметно добавляет в проект обработчики событий, которые правильно управляют этой ситуацией. Когда происходит событие, Windows пробуждает приложение и вызывает соответствующим обработчик событий, именно в этот момент может начать выполняться код программиста. Когда процедура обработчика событий заканчивается, приложение снова переведет себя в спящий режим, приказывая Windows разбудить себя, когда произойдет другое интересное событие. Наконец, предполагая, что ничего не произошло разрушительного и неверного, в некоторый момент Windows разбудит приложение и проинформирует его, что необходимо прекратить работу. В этот момент приложение предпримет все необходимые действия — например, выведет окно сообщения, спрашивающего у пользователя, не хочет ли он сохранить файл и затем спокойно завершится. Снова большая часть кода, который это делает, добавляется в проект неявно VB IDE, и программист никогда его не видит. Поток выполнения в типичном приложении GUI Windows выглядит примерно следующим образом: На этой диаграмме рамка с пунктирной границей указывает ту часть исполнимого кода, к которой VB IDE предоставляет программисту доступ и для которого можно писать исходный код,— это несколько обработчиков событий. Остальная часть кода недоступна для программиста, хотя его можно в некоторой степени определить, выбирая тип приложения при запросе к VB на создание проекта. Для нового проекта в VB появляется диалоговое окно, запрашивающее тип приложения, которое требуется создать,— стандартный EXE, ActiveX EXE, ActiveX DLL и т.д. После выбора VB IDE использует его для генерации всего необходимого кода в той части программы, которая находится вне пунктирной рамки на приведенной выше диаграмме. Диаграмма показывает ситуацию, когда выбрано создание проекта стандартного EXE, отличающегося от других типов проектов (например, ActiveX DLL вообще не имеет цикла сообщений, но вместо этого зависит от клиентов в отношении вызова методов), и при этом дается примерное представление о том, что происходит. Ранее было сказано, что в C# программист получает доступ ко всему коду, теперь необходимо это уточнить: все мельчайшие подробности относительно того, что происходит в цикле сообщений, надежно скрыты внутри различных DLL, которые написала компания Microsoft, но можно видеть методы верхнего уровня, вызывающие различные элементы обработки. Допустим, имеются доступы к коду, который начинает выполнение всей программы, к вызову библиотечного метода, который заставляет программу войти в цикл сообщений и переводит ее в спящее состояние и т.д. Имеется также доступ к исходному коду, создающему экземпляры всех различных элементов управления, которые помещаются на форме, делая эти экземпляры видимыми и определяя их начальные положения, размеры и все остальное. Необходимо отметить еще и тот момент, что не требуется писать никакой подобный код самостоятельно. При использовании Visual Studio.NET для создания проекта C# по-прежнему появляется диалоговое окно, спрашивающее, какой тип проекта необходимо создать, и Visual Studio.NET будет по-прежнему готовить весь базовый код. Различие состоит в том, что Visual Studio.NET записывает этот базовый код, как исходный на C#, который затем можно непосредственно редактировать. Все это приводит к тому, что исходный код получается более длинным и более сложным. Однако огромное его преимущество заключается в том, что он обеспечивает значительно большую гибкость в том, что делает программа и как она себя ведет. А значит, можно написать намного больше типов проектов в C#. В то время как в Visual Basic возможно написание только различных видов форм и компонентов COM, в C# вы в праве написать любую из различных типов программ, которые выполняются под Windows. Это включает, например, консольные приложения (командной строки) и страницы ASP.NET (наследник ASP), что нельзя написать в VB6 (можно использовать VBScript для страниц ASP). В этом приложении, однако, мы сосредоточимся на классических приложениях GUI для Windows. Код C# для оставшейся части программыВ этом разделе будет рассмотрена оставшаяся часть кода для примера SquareSample, в результате чего мы узнаем немного больше о классах в C#. Пример SquareRootна C# был создан в Visual Studio.NET, а пример на VB — в IDE VB6. Однако представленный здесь код является не совсем тем, который сгенерировала Visual Studio.NET. Помимо добавления обработчика событий, сделаны и другие изменения в коде, чтобы лучше проиллюстрировать принципы программирования в C#. Но несмотря ни на что, он все-таки даст хорошее представление о той работе, которую делает Visual Studio.NET, когда создает проект. Весь текст исходного кода достаточно длинный. Он представлен здесь для полноты, но лучше сразу перейти к последующим объяснениям и обращаться к исходному коду по мере необходимости. using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace Wrox.ProfessionalCSharp.AppendixC.SquareRootSample { /// <summary> /// Form, которая формирует основное окно приложения: /// </summary> public class SquareRootForm : System.Windows.Forms.Form { private System.Windows.Forms.TextBox txtNumber; private System.Windows.Forms.TextBox txtSign; private System.Windows.Forms.TextBox txtResult; private System.Windows.Forms.Button cmdShowResults; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows Forms.Label label3; private System.Windows.Forms.Label label4; /// <summary> /// Необходимые для designer переменные. /// </summary> private System.ComponentModel.Container components; public SquareRootForm() { InitializeComponent(); } public override void Dispose() { base.Dispose(); if(components != null) components.Dispose(); } #region Windows Form Designer generated code /// <summary> /// Требуемый для поддержки Designer метод - не изменять /// содержимое этого метода с помощью редактора кода. /// </summary> private void InitializeComponent() { this.txtNumber = new System.Windows.Forms.TextBox(); this.txtSign = new System.Windows.Forms.TextBox(); this.cmdShowResults = new System.Windows.Forms.Button(); this.label3 = new System.Windows.Forms.Label(); this.label4 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.txtResult = new System.Windows.Forms.TextBox(); this.SuspendLayout(); // // txtNumber // this.txtNumber.Location = new System.Drawing.Point(160, 24); this.txtNumber.Name = "txtNumber"; this.txtNumber.TabIndex = 0; this.txtNumber.Text = ""; // // txtSign // this.txtSign.Enabled = false; this.txtSign.Location = new System.Drawing.Point(160, 136); this.txtSign.Name = "txtSign"; this.tхtSign.TabIndех = 1; this.txtSign.Text = ""; // // cmdShowResults // this.cmdShowResults.Location = new System.Drawing.Point(24, 96); this.cmdShowResults.Name = "cmdShowResults"; this.cmdShowResults.Size = new System.Drawing.Size(88, 23); this.cmdShowResults.TabIndex = 3; this.cmdShowResults.Text = "Show Results"; this.cmdShowResults.Click += new System.EventHandler(this.OnClickShowResults); // // label3 // this.label3.Location = new System.Drawing.Point(72, 24); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(80, 23); this.label3.TabIndex = 6; this.label3.Text = "Input a number"; // // label4 // this.label4.Location = new System.Drawing.Point(80, 184); this.label4.Name = "label4"; this.label4.Size = new System.Drawing.Size(80, 16); this.label4.TabIndex = 7; this.label4.Text = "Square root is"; // // label1 // this.label1.Location = new System.Drawing.Point(112, 136); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(40, 23); this.label1.TabIndex = 4; this.label1.Text = "Sign is"; // // label2 // this.label2.Location = new System.Drawing.Point(48, 184); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(8, 8); this.label2.TabIndex = 5; // // txtResult // this.txtResult.Enabled = false; this.txtResult.Location = new System.Drawing.Point(160, 184); this.txtResult.Name = "txtResult"; this.txtResult.TabIndex = 2; this.txtResult.Text = ""; // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(292, 269); this.Controls.AddRange(new System.Windows.Forms.Control[] { this.label4, this.label3, this.label2, this.label1, this.cmdShowResults, this.txtResult, this.txtSign, this.txtNumber }); this.Name = "Form1"; this.Text = "Square Root C# Sample"; this.ResumeLayout(false); } #endregion /// <summary> /// Обработчик событий для нажатия пользователем кнопки Show Results /// Выводит квадратный корень и знак числа /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnClickShowResults(object sender, System.EventArgs e) { float NumberInput = float.Parse(this.txtNumber.Text); if (NumberInput < 0) { this.txtSign.Text = "Negative"; this.txtResult.Text = Math.Sqrt(-NumberInput) + " i"; } else if (NumberInput == 0) { txtSign.Text = "Zero"; txtResult.Text = "0"; } else { this.txtSign.Text = "Positive"; this.txtResult.Text = Math.Sqrt(NumberInput).ToString(); } } } class MainEntryClass { /// <summary> /// Основная точка входа приложения. /// </summary> [STAThread] static void Main() { SquareRootForm TheMainForm = new SquareRootForm(); Application.Run(TheMainForm); } } } Пространства именОсновная часть исходного кода SquareRootна C# начинается с объявлений пространств имен и класса: namespace Wrox.ProfessionalCSharp.AppendixC.SquareRootForm { public class SquareRootForm : System.Windows.Forms.Form { Класс SquareRootFormбудет содержать почти весь код — все методы и т.д. с небольшим объемом кода, находящимся в классе с именем MainEntryClass. Помните что легче всего здесь представлять класс как объект класса VB, хотя есть одно различие, состоящее в том, что реально виден исходный код, который начинается с объявления класса. В VB среда разработки — это просто отдельное окно, содержащее код класса. Пространство имен не имеет аналогии в VB и проще всего представить его как способ организации имен классов таким образом, как файловая система организует имена файлов. Например, почти наверняка на жестком диске имеется большое количество файлов, которые имеют имя ReadMe.txt. Если бы это имя было единственной информацией о каждом файле, то невозможно было бы различить все эти файлы. Но есть полные имена доступа, например, C:\Program Files\ReadMe.txtи G:\Program Files\HTML Help Workshop\ReadMe.txt. Пространства имен работают так же, но без дополнительных расходов, связанных с созданием реальной файловой системы — они являются по сути не более чем метками. Формально не требуется ничего делать, чтобы создать пространство имен, кроме просто объявления его в коде таким способом, как было сделано в примере выше. Код, представленный в нем, означает, что полное имя класса, который был определен, будет не SquareRootForm, a Wrox.ProfessionalCSharp.AppendixC.SquareRootForm. Крайне маловероятно, что кто-то будет записывать класс с этим полным именем. С другой стороны, если бы не было пространства имен, то существовал бы большой риск путаницы, так как кто-нибудь еще мог бы написать класс с именем SquareRootForm. Исключение конфликтов такого рода важно в C#, так как рабочая среда .NET использует только эти имена для идентификации классов, в то время как элементы управления ActiveX, созданные VB, применяют для ухода от конфликтов имен сложный механизм, включающий GUID. Компания Microsoft предпочла более простую концепцию пространств имен в связи с опасениями, что некоторые сложности COM, такие как GUID, сделают неоправданно трудным для разработчиков создание хороших приложений Windows. Хотя в C# пространства имен и не являются строго обязательными, настоятельно рекомендуется все классы помещать в пространство имен, чтобы предотвратить любые возможные конфликты имен с другим программным обеспечением. Фактически крайне редко можно увидеть код C#, который не начинается с объявления пространства имен. Пространства имен могут быть вложенными. Например, приведенный выше код пространства имен: namespace Wrox.ProfessionalCSharp.AppendixC.SquareRootSample { public class SquareRootForm : System.Windows.Forms.Form { // и т.д. } } можно было бы записать следующим образом: namespace Wrox { namespace ProfessionalCSharp { namespace AppendixC { namespace SquareRootSample { public class SquareRootForm : System.Windows.Forms.Form { // и т.д. } } } } } В этом коде добавлены закрывающие фигурные скобки, просто чтобы подчеркнуть, что они всегда должны соответствовать открывающим. Фигурные скобки используются для отметки границ пространств имен и классов так же, как они используются для отметки границ методов и составных инструкций. Инструкция usingКонечная часть приведенного выше кода, который начинает проект SquareRoot, состоит из инструкций using: using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace Wrox.ProfessionalCSharp.AppendixC.SquareRootSample { Эти инструкции usingприсутствуют здесь, чтобы упростить код. Полные имена классов, включающие имена пространств имен, будут длинными. Например, позже в этом коде определяется пара текстовых полей. Текстовое поле представляется классом System.Windows.Forms.TextBox. Если писать это в коде каждый раз при ссылке на Text Box, код будет выглядеть очень загроможденным. Вместо этого инструкция using System.Windows.Forms; дает задание компилятору найти в этом пространстве имен все классы, которые отсутствуют в текущем пространстве имен и для которых не определено пространство имен. Теперь можно просто писать TextBoxвезде, где необходимо сослаться на этот класс. Обычно любая программа на C# начинается с ряда инструкций using, вводящих все пространства имен, которые будут использоваться в множество пространств имен, просматриваемых компилятором. Пространства имен, определенные в приведенном выше коде охватывают различные части библиотеки базовых классов .NET, и поэтому позволяют, что очень удобно, использовать различные базовые классы .NET. Определение класса: наследованиеТеперь мы переходим к определению класса SquareRootForm. Само определение достаточно простое: public class SquareRootForm : System.Windows.Forms.Form { Ключевое слово class сообщает компилятору, что будет определен класс. Интерес в данном случае представляет двоеточие после имени класса, за которым следует другое имя — Form. Это момент, где мы вводим упоминавшуюся ранее важную концепцию, которую необходимо знать, чтобы понимать программирование на C#,— наследование Приведенный выше синтаксис сообщает компилятору, что класс SquareRootFormнаследуется из класса Form(в действительности из Windows.Forms.Form). Это означает, что класс получает не только все методы, свойства и т.д., которые мы определяем, он получает все, что было в классе Form. Formявляется очень мощным базовым классом .NET, который предоставляет все свойства базовой формы. Он содержит методы, позволяющие форме выводиться, и большое количество свойств, включая Height, Width, Desktop Location, BackColor(фоновый цвет формы), которые управляют внешним видом формы на экране. Наследуя от этого класса, наш собственный класс сразу получает все эти свойства и является поэтому полноценной формой. Класс, от которого наследуют, называется базовым классом, а новый класс называют производным классом. Если вы знакомы с интерфейсами, то наследование не должно быть для вас новым понятием, так как интерфейсы могут наследоваться друг из друга. Однако здесь имеется значительно более мощная конструкция, чем наследование интерфейсов. Когда интерфейс COM наследуется из другого интерфейса, он получает только имена и сигнатуры методов и свойства. Это, в конце концов, все, что содержит интерфейс. Однако класс содержит весь код, который реализует эти методы, и тому подобное также, как в VB делает объект класса. Это означает, что SquareRootFormполучает все реализации из класса Form, а также имена методов. Этот вид наследования называется наследованием реализации, он не является новинкой в C#: это была фундаментальная концепция классического объектно-ориентированного программирования (OOP), которой пользовались в течение десятилетий. Программы C++, в частности, обычно работают на основе этой концепции, но она не поддерживается VB. (Наследование реализации имеет сходство с созданием подклассов.) При разработке программ на C# можно обнаружить, что вся архитектура типичной программы C# почти всегда основывается на наследовании реализации. Но наследование реализации является еще более мощным средством. Как будет показано позже, когда класс наследуется из другого класса, он не обязан брать все реализации в базовом классе. При желании можно изменить реализации определенных методов и свойств с помощью технологии, называемой переопределением. Это означает, что можно создать класс, который очень похож на существующий, но имеет некоторые отличия в том. как и что он делает. Это существенно облегчает повторное использование кода, написанного другими людьми, сберегая тем самым время разработки. Также важно понять, что не требуется доступ к исходному коду базового класса, чтобы наследовать из него. По очевидным коммерческим соображениям компания Microsoft сохраняет исходный код класса Form для себя. Тот факт, что компилированная библиотека доступна в форме сборки, является достаточным, чтобы можно было наследовать от этого класса, используя требуемые методы и переопределяя методы, которые не нужны. Точка входа в программуОсновной точкой входа в программу является функция Main(): class MainEntryClass { /// <summary> /// Основная точка входа приложения. /// </summary> [STAThread] static void Main() { SquareRootForm TheMainForm = new SquareRootForm(); Application.Run(TheMainForm); } } Это не очевидная точка входа в программу, но это — она. Правило в C# говорит, что выполнение программы начинается с метода с именем Main(). Этот метод должен быть определен как статический в том же классе. Обычно должен быть только один метод во всех классах в исходном коде, который отвечает этому описанию в программе, иначе компилятор не будет знать, какой из них выбрать. Main()здесь определен без параметров и как возвращающий void(другими словами, не возвращающий ничего). Это не единственная возможная сигнатура этого метода, но это обычная сигнатура для приложения Windows (приложения командной строки получают параметры — это любые аргументы, задаваемые в командной строке).
Так как метод Main()должен быть в классе, то здесь присутствует класс с именем MainEntryClass. В этом классе нет ничего другого, но это не обязательно должно быть так, вполне допустимо для класса, в котором определена точка входа в программу, содержать и другие методы. Тот факт, что метол Main()— статический метод, является важным. Мы говорили раньше, что статические методы являются специальными методами, которые могут выполняться без реального создания в начале программы объекта класса. Так как при выполнении прежде всего вызывается метод Main(), то в этот момент не существует никаких экземпляров никаких классов — еще не выполнился никакой код для их создания. Вот почему точка входа должна быть статической. Помимо ключевого слова static, определение Main()выглядит, как и предыдущие рассмотренные определения методов. Однако перед ним стоит в квадратных скобках слово [STAThread], [STAThread]является примером атрибута — еще одной концепции, которая не имеет аналогов в исходном коде VB. Атрибут является конструкцией, предоставляющей дополнительную информацию компилятору о некоторых элементах кода, и всегда имеющей форму слова (возможно также с некоторыми параметрами, хотя не в данном случае) в квадратных скобках сразу перед элементом, к которому он применяется. Этот конкретный атрибут сообщает компилятору о модели потоков выполнения, в которой должен выполняться код. Детали моделей потоков выполнения здесь рассматриваться не будут, но можно сказать, что запись [STAThread]в исходном коде C# имеет эффект, аналогичный выбору модели потоков выполнения в Project Properties в VB IDE, хотя в VB это можно делать только для проектов ActiveX DLL и ActiveX Control. Отметим также, что эта аналогия только приблизительная, так как атрибут C# выбирает модель потоков выполнения .NET, а не модель потоков COM. Создание экземпляров классовДавайте теперь рассмотрим код внутри метода Main(). Прежде всего необходимо создать форму — другими словами, экземпляр объекта SquareRootForm. Это делает первая строка кода: SquareRootForm TheMainForm = new SquareRootForm(); Очевидно, что этот код нельзя сравнить с соответствующим кодом VB, потому что такие команды VB недоступны как исходный код, но можно сделать сравнение, если представить, что в некотором коде VB необходимо создать диалоговое окно. В VB это будет выглядеть примерно следующим образом: Dim SomeDialog As MyDialogClass Set SomeDialog = New MyDialogClass В этом коде VB сначала объявляется переменная, которая является объектной ссылкой — SomeDialogбудет ссылаться на экземпляр MyDialogClass. Затем реально создается экземпляр объекта с помощью ключевого слова Newиз VB и присваивается переменной ссылка на этот объект. Это совпадает с тем, что происходит в коде C#: объявляется переменная с именем TheMainForm, которая является ссылкой на объект SquareRootForm, затем используется ключевое слово C# newдля создания экземпляра SquareRootForm, и после этого мы задаем переменной ссылку на этот объект. Основное синтаксическое различие состоит в том, что C# позволяет объединить обе операции в одной инструкции таким же образом, как ранее сразу объявлялась и инициализировалась переменная NumberInput. Можно также заметить скобки после выражения new — это требование C#. При создании объектов всегда необходимо записывать эти скобки, потому что C# интерпретирует создание объекта несколько похоже на вызов метода, так что даже можно иногда передавать параметры в вызов new, чтобы указать, как желательно инициализировать новый объект. В данном случае параметры не передаются, но скобки все равно должны использоваться. Классы С#До сих пор говорилось, что классы C# похожи на модули классов в VB. Мы уже видели одно различие, заключающееся в том, что классы C# допускают статические методы. Приведенный выше код метода Main()подчеркивает теперь еще одно различие: если делается что-то подобное в VB, то необходимо также задать для созданного объекта значение Nothing, когда работа с ним будет закончена. Однако ничего подобного не появляется в коде C#, так как в C# этого делать вовсе не нужно. Причина этого различия состоит в том, что классы C# являются более эффективными и динамичными, чем их соответствующие аналоги в VB. Объекты классов VB являются на самом деле объектами COM, то есть каждый из них включает некоторый сложный код, который проверяет, сколько ссылок на объект поддерживается, поэтому каждый объект может разрушить себя, когда обнаружит, что он больше не нужен. В VB, если не задать объектную ссылку Nothingпосле завершения работы с объектом, это будет рассматриваться как плохая практика программирования, так как это означает, что объект не знает, что он больше не нужен, поэтому он может висеть в памяти возможно до окончания всего процесса. Однако по соображениям производительности объекты C# не выполняют проверку такого рода. Вместо этого C# использует механизм, называемый сборкой мусора. При этом вместо того, чтобы каждый объект проверял, что он должен все еще существовать, среда выполнения .NET время от времени передает управление так называемому сборщику мусора. Сборщик мусора исследует состояние памяти, используя очень эффективный алгоритм для идентификации тех объектов, которые больше не нужны коду, и удаляя их. При наличии такого механизма неважно сбрасываются ли ссылки, когда работа с ними закончена, обычно достаточно просто подождать, пока переменная выйдет из области видимости. Но если желательно задать ссылочную переменную, которая ни на что не указывает, то соответствующим ключевым словом C# является null, которое означает то же самое, что и Nothingв VB. Следовательно, там, где в VB было бы написано: Set SomeDialog = Nothing; в C# будет написано: TheMainForm = null; Заметим, что это само по себе делает не так уж много в C#, поскольку объект все равно не будет разрушен, пока не вызовется сборщик мусора. Вход в цикл сообщенийРассмотрим теперь конечную инструкцию в основном методе: Application.Run(TheMainForm); Эта инструкция запускает цикл сообщений. На самом деле здесь вызывается статический метод Run()класса System.Windows.Forms.Application. Этот метод обрабатывает цикл сообщений. Он переводит приложение (или, строго говоря, поток выполнения) в спящее состояние и просит Windows разбудить его, когда произойдет интересное событие. Метод Run()может получать один параметр, являющийся ссылкой на форму, которая будет обрабатывать все события. Run()заканчивается, когда произойдет и обработается событие, дающее указание форме завершить работу. Когда метод Run()подходит к концу, то и метод Main()завершается. Так как этот метод был точкой входа в программу, то по его завершении выполнение всего процесса останавливается. Один элемент синтаксиса в приведенных выше инструкциях, который может показаться удивительным, состоит в том, что при вызове метода Run()используются скобки, даже хотя никакое возвращаемое значение из этого метода не используется, и, следовательно, выполняется вызов, эквивалентный вызову подпрограммы в VB. VB в этом случае не требует скобок, но в C# существует правило, что при вызове метода всегда используются скобки.
Класс формы SquareRootМы видели, как C# запускает цикл сообщений, но мы еще не изучили процесс вывода и создания самой формы. Мы также не определились с вопросом о вызове обработчиков событий. Упоминалось, что Windows вызывает такие обработчики событий, как метод OnClickButtonResults(). Но как Windows узнает, что нужно вызвать этот метод? Мы найдем ответы на все наши вопросы в определении класса SquareRootFormи в его базовом классе Form. Отметим сначала, что класс SquareRootFormимеет достаточно много полей-членов. (Поле-член является выражением C# для переменной, которая определена как член класса. Можно представлять ее как переменную VB, которая имеет областью действия форму, или как переменную VB, которая определена в качестве члена модуля класса. Каждая такая переменная связана с определенным экземпляром класса — определенным объектом — и остается в области действия до тех пор, пока существует содержащий ее объект): public class SquareRootForm : System.Windows.Forms.Form { private System.Windows.Forms.TextBox txtNumber; private System.Windows.Forms.TextBox txtSign; private System.Windows.Forms.TextBox txtResult; private System.Windows.Forms.Button cmdShowResults; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label3; private System.Windows.Forms.Label label4; Каждое из этих полей соответствует одному из элементов управления. Можно легко увидеть три текстовых поля и кнопку. Имеются также четыре метки, соответствующие областям текста на форме. Мы не будем ничего делать с этими метками, поэтому не стоит беспокоиться и давать им какие-то более понятные пользователю имена. Однако каждая из этих переменных является просто ссылкой на объект, поэтому тот факт, что эти переменные существуют, не означает существования никаких экземпляров этих объектов — экземпляры объектов должны быть созданы отдельно. Процесс создания экземпляров этих элементов управления осуществляется с помощью так называемого конструктора. Конструктор в C# является примерным аналогом таким подпрограммам, как Form_Load(), Form_Initialize(), Class_Load()и Class_Initialize(). Это специальный метод, который автоматически вызывается, когда создается экземпляр класса, и он содержит код, необходимый для инициализации экземпляра. Конструктор в классе легко опознать, так как он всегда имеет такое же имя, как и сам класс. В данном случае мы ищем метод с именем SquareRootForm: public SquareRootForm() { InitializeComponent(); } Отметим, что так как это — конструктор, а не метод, который можно вызывать, он не определяет никакого возвращаемого типа. Однако после его имени стоят скобки, как и у метода. Можно использовать эти скобки для определения параметров, которые будут передаваться в конструктор (вспомните, что при создании переменной можно передавать параметры в скобках после предложения new). Определение конструктора указывает, нужны ли какие-то параметры для создания экземпляра объекта. Однако здесь нет никаких параметров. Мы увидим конструкторы, которые получают параметры, в примере Employee, рассматриваемом дальше в этом приложении. В данном случае конструктор просто вызывает метод InitializeComponent(). Это в действительности связано с Visual Studio.NET. Visual Studio NET имеет все те же свойства, что и IDE VB6 для графических манипуляций элементами управления — щелчок мышью для размещения элементов управления на форме и т.д. Однако, так как теперь в C# определения всех элементов управления задаются в исходном коде, Visual Studio.NET должна иметь возможность прочитать исходный код, чтобы определить, какие элементы управления находятся на форме. Она делает это, разыскивая метод InitializeComponent()и определяя, экземпляры каких элементов управления там создаются. InitializeComponent()является большим методом, поэтому он будет показан здесь полностью. Начинается он следующим образом: private void InitializeComponent() { this.txtNumber = new System.Windows.Forms.TextBox(); this.txtSign = new System.Windows.Forms.TextBox(); this.cmdShowResults = new System.Windows.Forms.Button(); this.label3 = new System.Windows.Forms.Label(); this.label4 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.txtResult = new System.Windows.Forms.TextBox(); Показанный код является множеством вызовов для реального создания экземпляров всех элементов управления на форме. Этот фрагмент кода не содержит на самом деле ни одного нового элемента синтаксиса C#, который бы до сих пор не встречался. Следующая часть кода начинает задавать свойства элементов управления: // // txtNumber // this.txtNumber.Location = new System.Drawing.Point(160, 24); this.txtNumber.Name = "txtNumber"; this.txtNumber.TabIndex = 0; this.txtNumber.Text = ""; // // txtSign // this.txtSign.Enabled = false; this.txtSign.Location = new System.Drawing.Point(160, 136); this.txtSign.Name = "txtSign"; this.txtSign.TabIndex = 1; this.txtSign.Text = ""; Этот код задаёт начальные позиции и начальный текст двух элементов управления, текстового поля ввода и текстового поля, которое выводит знак заданного числа. Новый элемент кода состоит в том что положение относительно верхнего левого угла экрана задается с помощью Point. Pointявляется базовым классом .NET (строго говоря, структурой), который содержит x- и y-координаты. Синтаксис двух строк, задающих Location, является инструктивным. Свойство TextBox.Locationявляется просто ссылкой на Point, поэтому, чтобы задать ему значение, необходимо создать и инициализировать объект Point, содержащий правильные координаты. Это первое использование нами конструктора с параметрами — в данном случае горизонтальной и вертикальной координат Pointи, следовательно, элемента управления. Если было бы желательно транслировать одну из этих строк в VB, предполагая, что был определён некоторый модуль класса VB с именем Point, и мы имели бы класс, который имеет такое свойство, то лучшее, что можно было бы сделать, выглядело бы примерно следующим образом: Dim Location As Point Set Location = New Point Location.X = 160 Location.Y = 24 SomeObject.Location = Location Это сравнимо со следующим кодом на C#: someObject.Location = new System.Drawing.Point(160, 24); Относительная компактность эквивалентной инструкции C# должна быть очевидна. Теперь рассмотрим те же команды для кнопки. В этом случае можно наблюдать задание свойств такого же вида, но здесь имеется другая вещь, которую необходимо выполнить: приказать Windows вызвать наш обработчик событий, когда нажимается кнопка. Это делает последняя строка из нижеследующих. this.cmdShowResults.Name = "cmdShowResults"; this.cmdShowResults.Size = new System.Drawing.Size(88, 23); this.cmdShowResults.TabIndex = 3; this.cmdShowResults.Text = "Show Results"; this.cmdShowResults.Click +=
Здесь происходит следующее. Кнопка, обозначенная как объект кнопки cmdShowResults, содержит событие Click, которое будет инициироваться, когда пользователь на нее нажмет. Надо добавить для этого события собственный обработчик событий. Сейчас C# не разрешает передавать имена методов непосредственно, вместо этого они должны помещаться в так называемый объект делегат. Детали этого здесь не рассматриваются, они приведены в главе 6 данной книги, но это делается для обеспечения безопасности типов. Вследствие такого действия появляется текст new System.EventHandler()в этом коде. Когда имя обработчика событий будет спрятано, мы добавим его к событию, используя оператор +=, который будет рассмотрен ниже. Арифметические операторы присваивания Символ +=представляет в C# так называемый оператор сложения-присваивания. Он дает удобное сокращение для случаев, когда необходимо добавить некоторую величину к другой величине. Это работает следующим образом. Пусть в VB объявлены два целых числа А и В и необходимо записать следующее выражение: В = В + А В C# можно записать похожим образом: В = В + А; Однако в C# для этого существует альтернативная сокращенная запись: B += А; +=в действительности означает "сложить значение выражения справа с переменной слева" и это работает для всех числовых типов данных, а не только для целых. Существуют также другие аналогичные операторы *=, /=и -=, которые соответственно умножают, делят и вычитают величину слева из переменной справа. Поэтому, например, чтобы разделить число на 2 и присвоить результат снова В, можно написать: B /= 2; Хотя в этом приложении не рассматриваются подробности, но C# имеет другие операторы, представляющие побитовые операции, а также дающие остаток при делении, и почти все они имеют соответствующие операторы операция-присваивание (см. главу 3). В примере SquareRootFormоператор сложения-присваивания применен к событию, строка: this.cmdShowResults.Click += new SyBtem.EventHandler(this.OnClickShowResults) означает: "добавить этот обработчик к событию". Может быть немного удивительным увидеть оператор, подобный +=, который применяется к чему-то, что не является таким простым числовым типом данных, как intили float, но это на самом деле иллюстрирует важный момент в отношении операторов в C# по сравнению с операторами в VB:
Приведенное выше утверждение необходимо немного уточнить. Чтобы применять эти операторы к другим типам объектов, необходимо сначала сообщить компилятору, что эти операторы означают для других типов объектов — процесс, называемый перезагрузкой операторов. Он работает примерно следующим образом. Предположим, что необходимо написать класс, который представляет, скажем, математический вектор. В VB это можно закодировать как модуль класса, который позволит написать: Dim V1 As Vector Set V1 = New Vector В математике векторы можно складывать, что будет обеспечено с помощью перезагрузки операторов. Но VB6 не поддерживает перезагрузку, поэтому вместо этого в VB6 вероятно придется определить метод Add для Vector, и, таким образом, сделать следующее: ' V1, V2 и V3 являются векторами Set V3 = V1. Add(V2) В VB это лучшее, что можно придумать. Однако в C#, если определить класс Vector, можно добавить в него перезагруженный оператор для +, который является по сути методом, имеющим имя operator+, и который компилятор будет вызывать, если увидит +, примененный к Vector. Это означает, что в C# можно будет написать: // V1, V2 и V3 являются векторами V3 = V1 + V2; Очевидно, что перезагруженные операторы не будут определяться для всех классов. Для большинства классов не будут иметь смысла действия типа сложения или умножения объектов. Однако для классов, для которых это имеет смысл, перезагрузка операторов может сделать код значительно проще для восприятия. Именно это и происходит с событиями. Поскольку имеет смысл говорить о добавлении обработчика к событию, был предоставлен перезагруженный оператор, чтобы позволить делать это, используя интуитивно понятный синтаксис с помощью операторов +(и +=). Можно также использовать -или -=для удаления обработчика из события. Подводя итогМы получили максимум возможного из рассмотрения примеров SquareRootForm. Существует значительный объем кода C#. который не был рассмотрен в версии C# этого приложения, но этот дополнительный код связан в основном с заданием различных других элементов управления на форме, и не вводи никаких новых принципов, поэтому мы не заострили на нем внимание. К этому моменту мы получили представление о синтаксисе C#. Мы видели, что он позволяет писать инструкции, которые значительно короче, чем соответствующий код VB. Мы также заметили, что C# помещает весь код в исходный файл в отличие от VB, где большая часть базового кода скрыта от программиста, что делает код проще за счет уменьшения гибкости при создании приложений. Мы также познакомились с концепцией наследования. Однако мы пока еще не видели реального примера некоторого кода, который можно написать на C#, но крайне трудно создать код, делающий то же самое на VB. Мы собираемся рассмотреть такой пример в следующем разделе, где будут с помощью некоторых классов проиллюстрированы возможности наследования. Пример: Employees и ManagersДля этого примера предположим, что пишется приложение, делающее некоторую обработку данных, имеющих отношение к сотрудникам компании. Для нас неважно, какую обработку оно включает, больший интерес представляет факт, что для этого достаточно полезно будет написать класс C# (или модуль класса VB), представляющий сотрудников. Мы предполагаем, что это будет формировать часть программного пакета, который можно продавать компаниям, чтобы помочь им при выплате зарплаты и т.д. Модуль класса Employee в VBСледующий код представляет попытку закодировать модуль класса Employeeна VB. Модуль класса предоставляет два открытых свойства: EmployeeNameи Salary, а также открытый метод GetMonthlyPayment(), возвращающий сумму, которую компания должна платить сотруднику каждый месяц. Это не совпадает с зарплатой частично потому, что зарплата предполагается выплачиваемой за год, и частично потому, что позже будет представлена возможность прибавления других выплат компании сотруднику (таких, как бонусы за производительность): ' локальные переменные для хранения значений свойств Private mStrEmployeeName As String ' локальная копия Private mCurSalary As Currency ' локальная копия Public Property Let Salary(ByVal curData As Currency) mCurSalary = curData End Property Public Property Get Salary() As Currency Salary = mCurSalary End Property Public Property Get EmployeeName() As String EmployeeName = mStrEmployeeName End Property Public Sub Create(sEmployeeName As String, curSalary As Currency) mStrEmployeeName = sEmployeeName mCurSalary = curSalary End Sub Public Function GetMonthlyPayment() As Currency GetMonthlyPayment = mCurSalary/12 End Function В реальной жизни будет написано, по-видимому, что-то более сложное, но и этого класса будет достаточно для иллюстрации рассматриваемых нами концепций. Фактически мы уже имеем проблему с этим модулем класса VB — имена большинства людей меняются не очень часто, вот почему свойство EmployeeNameпредназначено только для чтения. Это по-прежнему оставляет необходимость задавать имя в первый раз. Для этого добавлен метод Create, который определяет имя и зарплату. Таким образом, процесс создания объекта сотрудника будет выглядеть так: Dim Britney As Employee Set Britney = New Employee Britney.Create "Britney Spears", 20000 Эта схема работает, но она не очень удобна. Проблема с инициализацией объекта Employeeсостоит в том, что хотя VB предоставляет для этой цели методы Class_Loadи Class_Initialize, метод Class_Loadне может получать никаких параметров. Это означает, что нельзя выполнить никакой инициализации, которая является специфической для данного экземпляра Employee, поэтому необходимо просто написать отдельный метод инициализации Createи надеяться, что все, кто пишет клиентский код, никогда не будут забывать его вызывать. Такое решение неудобно, так как нет никакого смысла иметь объект Employee, у которого не заданы имя и зарплата, но именно это присутствует в приведенном выше коде в течение короткого периода между созданием экземпляра Britneyи инициализацией объекта. Пока будут помнить о вызове метода Create, все будет нормально, но здесь имеется потенциальный источник ошибок. В C# ситуация совершенно другая. Здесь в конструкторы можно подставлять параметры (эквивалент в C# для метода Class_Load). Необходимо только убедиться, что при определении класса Employeeв C# конструктор получает Nameи Salaryв качестве параметров. В C# можно будет написать: Employee Britney = new Employee("Britney Spears", 20000.00M); что значительно изящнее и менее подвержено ошибкам. Отметим кстати символ " М", добавленный к зарплате. Это связано с тем, что эквивалент C# для типа Currencyиз VB называется десятичным значением и ' M', добавленный к числу в C#, подчеркивает, что число надо интерпретировать как decimal. Его указывать не обязательно, но это полезно для дополнительной проверки во время компиляции. Класс Employee в C#Помня о приведенных выше замечаниях можно теперь представить первое определение версии C# класса Employee(отметим, что здесь показано определение класса, а не определение содержащего его пространства имен): class Employee { private readonly string name; private decimal salary; public Employee(string name, decimal salary) { this.name = name; this.salary = salary; } public string Name { get { return name; } } public virtual decimal Salary { get { return salary; } set { salary = value; } } public decimal GetMonthlyPayment() { return salary/12; } public override string ToString() { return "Name: " + name + ", Salary: $" + salary.ToString(); } } Просматривая этот код, мы видим сначала пару закрытых переменных — так называемых полей-членов, соответствующих переменным-членам в модуле класса VB. Поле nameпомечено как readonly. Мы скоро узнаем его точное значение. Грубо говоря, это гарантирует, что данное поле задано, когда создавался объект Employee, и не может впоследствии изменяться. В C# обычно не используют "венгерский" стиль именования объектов для имен переменных, поэтому они просто называются nameи salary, а не mStrEmployeeNameи mCurSalary. "Венгерский" стиль именования объектов означает, что имена переменных имеют префикс из букв, который указывает их тип ( mStr, mCurи т.д.). Это на сегодня неважно, так как редакторы являются более развитыми и могут автоматически предоставить информацию о типах данных. Поэтому рекомендуется не использовать "венгерский" стиль именования объектов в программах C#. В классе Employeeсуществует также конструктор, пара свойств — Nameи Salary, а также два метода — GetMonthlyPayment()и ToString(). Все это будет рассмотрено далее.
Конструктор EmployeeПосле объявления полей в приведенном выше коде располагается "метод", имя которого — Employee, совпадает с именем класса, то есть перед нами находится конструктор. Однако этот конструктор получает параметры и делает то же самое, что и метод Createв версии VB — он использует параметры для инициализации полей-членов: public Employee(string name, decimal salary) { this.name = name; this.salary = salary; } Существует потенциальная синтаксическая проблема, так как явные имена параметров совпадают с именами полей — nameи salary. Но она разрешается с помощью использования ссылки this, помечающей поля. Можно было бы вместо этого дать параметрам другие имена, но способ, которым это было сделано, является достаточно ясным и означает, что параметры сохраняют очевидные простые имена, которые соответствуют их значениям. Это обычный способ действий для C# в таких ситуациях. Теперь можно объяснить точное значение квалификатора readonlyперед именем поля: private readonly string name; Если поле помечено как readonly, то единственным местом, где ему может быть присвоено значение, является конструктор класса. Компилятор будет инициировать ошибку, если встретит код, который попытается изменить значение переменной readonly, в любом месте, кроме конструктора. Это предоставляет надежную гарантию, что переменная не будет изменена, если она была задана. Невозможно сделать что-либо подобное в VB, так как VB не имеет конструкторов, которые получают параметры, поэтому переменные уровня класса в VB должны быть инициализированы с помощью методов или свойств, вызываемых после создания экземпляра объекта. Между прочим этот конструктор не просто позволяет задать параметры для инициализации объекта Employee— он заставляет это сделать. Если написать код следующего вида: Employee Britney = new Employee; // неправильно то он на самом деле не откомпилируется. Компилятор будет инициировать ошибку, так как в C# должен всегда вызываться конструктор, когда создается новый объект. Но никаких параметров задано не было, а единственный доступный конструктор требует двух параметров. Поэтому просто невозможно создать объект Employeeбез каких-либо параметров. Это страхует от ошибок, вызываемых неинициализированными объектами Employee. Можно задать в классе более одного конструктора, чтобы выбрать, какое множество желательно использовать при создании нового объекта этого класса. Мы увидим, как это делается позже в данном приложении. Однако для этого конкретного класса единственного конструктора вполне достаточно. Свойства класса EmployeeТеперь мы переходим к свойствам Nameи Salary. Синтаксис C# для объявления свойства существенно отличается от соответствующего синтаксиса VB, но базовые принципы одинаковы. Необходимо определить два метода доступа (accessors) соответственно для "получения" и "задания" значений свойства. В VB они синтаксически интерпретируются как методы, но в C# свойство объявляется в целом, а затем определяются методы доступа внутри определения свойства. public decimal Salary { get { return salary; } set { salary = value; } } В VB компилятор знает, что определяется свойство, так как используется ключевое слово Property. В C# эта информация передается тем, что за именем свойства немедленно следует открывающая фигурная скобка. Если определяется метод, то это будет открывающая скобка, указывающая начало списка параметров, в то время как для поля это будет точка с запятой, отмечающая конец определения. Еще один момент, на который необходимо обратить внимание, состоит в том, что определения методов доступа getи setне содержат никаких списков параметров, это не важно. Мы знаем, что Salaryявляется десятичным значением, и метод доступа getвернет десятичное значение, не используя параметры, в то время как метод доступа setбудет получать один десятичный параметр и возвращать void. Для процедуры доступа setэтот параметр не объявляется явно, но компилятор всегда интерпретирует слово value как ссылающееся на него.
Так же как в VB, если необходимо сделать свойство предназначенным только для чтения, то просто опускается метод доступа set, как было сделано для свойства Name: public string Name { get { return name; } } Методы класса EmployeeВ классе Employeeсуществуют два метода — GetMonthlySalary()и ToString(). GetMonthlySalary()не требует комментариев, так как большая часть соответствующего синтаксиса C# уже была рассмотрена. Берется зарплата, делится на 12 для преобразования из годовой в месячную зарплату, и возвращается результат: public decimal GetMonthlyPayment() { return salary/12; } Единственным новым элементом синтаксиса здесь является инструкция return. В VB возвращаемое из метода значение определяют, задавая требуемое значение фиктивной переменной, которая имеет такое же имя, как и функция GetMonthlyPayment = mCurSalary/12 В C# тот же самый результат получают, добавляя параметр в инструкцию return(без скобок). Также returnв C# определяет, что происходит выход их функции, поэтому инструкция C#: return salary/12; эквивалентна в действительности следующему коду VB: GetMonthlyPayment = mCurSalary/12 Exit Function Метод ToString()более интересен. В большинстве случаев при написании класса C# будет полезным создание метода ToString(), который может использоваться для получения быстрого просмотра содержимого объекта. Как упоминалось ранее, метод ToString()уже доступен, так как все классы наследуют его от System.Object. Однако версия в System.Objectвыводит только имя класса и никаких данных из экземпляра класса. Компания Microsoft уже переопределила этот метод для всех числовых типов данных ( int, floatи т.д.), чтобы выводить реальные значения переменных, и нелишне будет сделать то же самое для собственных классов программиста. В любом случае это может быть полезно для просмотра содержимого объекта во время отладки: publiс override string ToString() { return Name: " + name + ", Salary: $" + salary.ToString(); } Эта переопределенная версия выводит имя и зарплату сотрудника. Новым элементом синтаксиса является то, что метод специально объявлен как override. C# требует, чтобы переопределяемые версии методов помечались явно, и будет инициировать ошибку компиляции, если этого не сделать. Это исключает риск любых потенциальных ошибок, когда, например, мы можем случайно переопределить метод без его реализации, возможно не зная, что метод с таким именем уже присутствует в базовом классе. Мы завершили пример класса Employeeкак в VB, так и в C#, и до сих пор, хотя имеются некоторые неровности в создании и инициализации экземпляра Employeeв версии VB, оба языка справились достаточно хорошо с требованиями. Однако одна из целей этого приложения состоит в том, чтобы показать, почему C# может быть в отдельных ситуациях значительно более мощным, чем VB6. Мы будем добавлять некоторые свойства в версию C# нашего примера, которые оставят VB далеко позади. Начнем со статических полей и свойств. Статические членыМы упоминали несколько раз, что в C# классы имеют специальные методы, называемые статическими, которые можно вызвать, не создавая экземпляр объекта. Эти методы не имеют никакой аналогии в VB. Фактически, статическими могут быть не только методы, но и поля, свойства или любые другие члены класса.
Чтобы проиллюстрировать, как работают статические члены и почему их необходимо использовать, давайте представим себе, что мы хотели бы, чтобы класс Employee поддерживал извлечение названия (имени) компании, в которой работает каждый сотрудник. Здесь имеется существенное различие между названием компании и именем сотрудника, так как каждый объект сотрудника представляет обособленную единицу, и поэтому необходимо хранить различные имена сотрудников. Это обычное поведение переменных модулей классов в VB и поведение по умолчанию полей в C#. Но если организация купила программное обеспечение, которое содержит класс Employee, то очевидно, что на всех сотрудников приходится одно и то же название компании. Это означает, что было бы избыточно хранить имя компании отдельно для каждого сотрудника. Будет просто ненужное дублирование строки. Вместо этого мы хотим сохранить имя компании только один раз и затем предоставить доступ к этим данным каждому объекту сотрудника. Именно так работает статическое поле. Объявим такое поле как companyName: class Employee { private string name; private decimal salary; private static readonly string companyName; В этом коде объявлено еще одно поле, но, помечая его как static, мы инструктируем компилятор, что эту переменную нужно сохранить только один раз, независимо от того, сколько создано объектов Employee. В реальном смысле это статическое поле ассоциируется с классом как целым, а не с каким-то одним объектом. Мы также объявили это поле используемым только для чтения. Такое указание имеет смысл, потому что название компании, так же как имя сотрудника, не должно меняться после запуска программы. Конечно, одного объявления этого поля не достаточно. Необходимо также убедиться, что оно инициализируется правильными данными. Где это нужно сделать? Ясно, что не в конструкторе — конструктор вызывается всякий раз при создании объекта Employee, в то время как companyNameнужно инициализировать только однажды. C# предоставляет для этой цели так называемый статический конструктор, который действует как любой другой конструктор, но работает для класса в целом, а не для определенного объекта. Если для класса определить статический конструктор, то он будет выполняться только один раз. Не гарантируется точно, когда он сработает, но это произойдет до того, как любой клиентский код попытается получить доступ к классу. Это обычно происходит при первом запуске программы. Добавим статический конструктор для класса Employee: static Employee { companyName = "Wrox Press Pop Stars"; } Как обычно, конструктор идентифицируется по имени, которое совпадает с именем класса. Этот конструктор обозначается также как static, следовательно, он является статическим конструктором. Он не помечен ни как public, ни как private, так как он не будет вызываться никаким кодом C#, а только средой выполнения .NET. Поэтому для статического конструктора не требуется модификатор доступа. В нашем примере статический конструктор был реализован с жестко закодированным названием компании. Еще реальнее было бы прочитать запись в реестре или файл или соединиться с базой данных, чтобы найти название компании. Между прочим, поскольку поле companyName объявлено как статическое и только для чтения, то статический конструктор является единственным местом, где полю можно законно присвоить значение. Осталось сделать одну последнюю вещь — определить открытое свойство, которое позволяет получить доступ к названию компании. public static string сompanyName { get { return companyName; } } Свойство companyNameтакже было объявлено как статическое, и теперь можно видеть реальное значение статическою метода или свойства метод или свойство может быть объявлен как статический, если он обращается только к статическим полям, и не обращается ни к каким данным, которые ассоциируются с определенным объектом. Как мы уже видели, синтаксис вызова статических членов класса извне класса слегка отличается от используемого для других членов, так как статический член ассоциирован с классом, а не с каким-то объектом, то для его вызова используется имя класса, а не имя переменной: string Company = Employee.CompanyName; Концепция статических членов является очень мощной и предоставляет полезные средства классу для реализации любой функциональности, которая является одинаковой для каждого объекта этого класса. Единственным способом, которым можно добиться чего-то подобного в VB, является определение глобальных переменных. Однако, если сделать это, то глобальные переменные имеют недостаток, заключающийся в том, что они не связаны с каким-то классом, что ведет также к вопросам конфликта имен. Другими ситуациями, где используются статические члены класса, являются: □ Возможная реализация свойства MaximumLengthдля нашего класса Employeeили любого другого класса, содержащего имя, если необходимо определить максимальную длину имени. □ В C# большинство цифровых типов данных имеют статические свойства, которые указывают их максимальные значения. Можно, например, определить наибольшие значения, которые хранятся в intи float: int MaxIntValue = int.MaxValue; float MaxFloatValue = float.MaxValue; НаследованиеТеперь мы собираемся с помощью примера рассмотреть, как работает реализация наследования. Давайте предположим, что около года назад был поставлен пакет программного обеспечения и пришло время для следующей версии. Заказчики сделали замечание, что некоторые из их сотрудников являются на самом деле менеджерами, а менеджеры обычно получают бонусы в зависимости от прибыли, а также обычною зарплату. Это означает что метод GetMonthlyPayment()не дает полной информации для менеджеров. Вывод: необходимо добавить некоторый способ учета менеджеров. Для целей нашего примера будем предполагать, что бонус является некоторой постоянной величиной, которую можно определить при создании менеджера. Мы откажемся в данном случае выполнять какие-либо специальные вычисления, связанные с прибылью. Как можно при создании кода в VB обновить наше приложение? Существуют два возможных подхода, но оба они имеют серьезные недостатки. Можно: □ Написать новый класс Manager □ Изменить класс Employee Создание нового класса является, вероятно, тем подходом, который потребует меньше всего работы так как можно начать с простого копирования кода модуля класса Employeeи затем изменить эту копию кода. Проблема состоит в том, что Employeeи Managerимеют такой большой объем общего кода, как весь код, связанный со свойствами Name, CompanyNameи Salary. Дублирование одного и того же кода является опасным. Что произойдет, если в некоторый момент какая-то причина заставит изменить код? Плохой разработчик надеется не забыть внести одинаковые изменения в оба класса. Это самый простой способ создания ошибок. Другая проблема состоит в том, что теперь существуют два несвязанных класса Employeeи Manager, с которыми должен иметь дело код клиента, что скорее всего сделает его написание затруднительным. (Хотя можно обойти эту проблему, помещая общие свойства в интерфейс и реализуя этот интерфейс в обоих классах Employeeи Manager.) Альтернативным способом является написание класса Managerи размещение объекта Employeeвнутри него как переменной с областью действия класса. Это решает проблему дублирования кода, но по-прежнему оставляет нас с двумя различными объектами, а также с неудобным, непрямым синтаксисом для вызова методов и свойств сотрудника ( objManager.objEmployее.Nameи т.д.). Если выбрать модификацию модуля класса сотрудника, то по-видимому надо добавить дополнительное поле типа Boolean, которое указывает, является ли Employeeменеджером или нет. Затем в соответствующих частях кода это Booleanбудет проверяться в инструкции if, чтобы знать, что делать. Это решает проблему двух несвязанных классов — снова имеется только один класс. Однако это вносит новую трудность: как было специально сказано ранее, поддержку для менеджеров решено было добавить примерно год спустя. Это означает, что модуль класса Employeeбыл по-видимому поставлен, протестирован, полностью отлажен и известно, что он работал правильно. В этой ситуации вряд ли возникнет желание обращаться к работающему коду, чтобы изменить его, учитывая связанный с этим риск внесения новых ошибок. Другими словами, мы достигли точки, где VB не может предложить никакого удовлетворительного решения. Из заголовка этого раздела нетрудно сделать заключение, что C# предлагает способ решения этой проблемы с помощью использования наследования. Мы уже видели, что наследование включает добавление или замену свойств классов. В предыдущем примере класс SquareRootFormдобавил код к классу .NET System.Windows.Forms.Form. Он определил элементы управления для размещения в SquareRootFormкак поля-члены, а также добавил обработчик событий. В примере Employeeбудут продемонстрированы как добавление, так и замена свойств базового класса, а также определен класс Manager, который является производным из класса Employee. Мы добавим поле и свойство, представляющие бонус, и заменен метод GetMonthlyPayment()(для полноты также будет заменен метод ToString(), чтобы он выводил бонус вместе с именем и зарплатой). Все это означает, что будет получен отдельный класс. Но при этом не потребуется дублировать никакой код и вносить большие изменения в класс Employee. Может показаться, что по-прежнему существует проблема двух различных классов, что делает более трудным написание клиентского кода, но, как будет продемонстрировано позже, C# имеет ответ и на это. Наследование от класса EmployeeПрежде чем определить класс Manager(), необходимо внести одно маленькое изменение в классе Employee: public virtual decimal GetMonthlyPayment() { return salary/12; } что сделает метод GetMonthlyPayment()виртуальным ( virtual). Это способ, которым C# сообщает, что данный метод в принципе может быть переопределен.
Класс ManagerТеперь можно определить класс Manager(): class Manager : Employee { private decimal bonus; public Manager(string name, decimal salary, decimal bonus) : base(name, salary) { this.bonus = bonus; } public Manager(string name, decimal salary) : this(name, salary, 100000M) { } public decimal Bonus { get { return bonus; } } public override string ToString() { return base.ToStrint() + ", bonus: " + bonus; } public override decimal GetMonthlyPayment() { return base.GetMonthlyPayment() + bonus/12; } } Помимо почти завершенной реализации класса Employee, который был унаследован, Managerсодержит следующие члены: □ Поле bonus, которое будет использоваться для хранения бонуса менеджера, и соответствующее свойство. □ Перезагруженный метод GetMonthlyPayment(), а также новую перегруженную версию метода ToString(). □ Два конструктора. Поле bonusи соответствующее свойство Bonusне требуют дальнейших обсуждений. Однако мы внимательно рассмотрим переопределенные методы и новые конструкторы, так как они будут иллюстрировать важные свойства языка C#. Переопределение методаПереопределенная версия метода GetMonthlyPayment()является достаточно простой. Отметим, что она помечена ключевым словом overrideдля сообщения компилятору, что мы переопределяем метод базового класса, как это делалось с методом Employee.ToString(): public override decimal GetMonthlyPayment() { return base.GetMonthlyPayment() + bonus/12; } Переопределенная версия содержит также вызов версии этого метода из базового класса. При этом используется новое ключевое слово base, baseдействует таким же образом, как и this, за исключением того, что оно специально указывает, что надо использовать метод или свойство и т.д. из определения базового класса. При желании можно альтернативно реализовать переопределенную версию метода GetMonthlyPayment()следующим образом: public override decimal GetMonthlyPayment() { return (Salary + bonus)/12; } но, чтобы показать использование ключевого слова base, был выбран другой вариант. В связи с этим есть одно действие, которое мы не смогли бы сделать: public override decimal GetMonthlyPayment() { return (salary + bonus)/12; // неправильно } Код выглядит почти так же, как предыдущая версия, кроме того, что поле salaryиспользуется непосредственно, а не через свойство Salary. Можно предположить, что это более эффективное решение, поскольку фактически убирается вызов метода. Но компилятор будет инициировать ошибку, так как поле salaryобъявлялось как private(закрытое). Этот означает, что ничему вне класса Employeeне разрешается видеть это поле. Даже производные классы не знают о закрытых полях базового класса. Если необходимо, чтобы производные, но не связанные классы могли видеть поле, C# предоставит альтернативный уровень защиты protected(защищенный): protected decimal salary; // можно сделать так Если член класса объявлен как защищенный, то он виден только в этом классе и в производных классах. Однако обычно строго рекомендуется сохранять все поля закрытыми ( private) по той же причине, по которой требуется сохранять переменные закрытыми в модулях классов VB. Дело в том, что при сокрытии реализации класса (или модуля класса) облегчается выполнение будущего обслуживания этого класса. Обычно модификатор protectedиспользуется для свойств и методов, которые предназначены только для того, чтобы разрешать производным классам получать доступ к свойствам определения базового класса. Конструкторы класса ManagerДавайте добавим по крайней мере один конструктор для класса Managerв связи с тем, что: □ Существует дополнительный элемент информации — бонус менеджера, который необходимо определить, когда создается экземпляр объекта Manager. □ В отличие от методов, свойств и полей, конструкторы не наследуются производными классами. Фактически было добавлено два конструктора. Это связано с решением, что бонус менеджера обычно по умолчанию равен $100000, если он не определен явно. В VB можно определить в методах параметры по умолчанию, но C# не разрешает делать это напрямую. Вместо этого C# предлагает более мощную технику — перезагрузку методов, которая дает тот же результат. Определение в данном случае двух конструкторов позволяет проиллюстрировать эту технику. Первый конструктор Manager имеет три параметра: public Manager(string name, decimal salary, decimal bonus) : base(name, salary) { this.bonus = bonus; } Прежде всего отметим вызов конструктора базового класса с помощью немного странного синтаксиса. Этот синтаксис называют инициализатором конструктора (constructor initializer.) При этом любому конструктору разрешается вызвать один другой конструктор перед своим выполнением. Этот вызов делается в инициализаторе конструктора с помощью показанного выше синтаксиса. Конструктор может вызвать либо другой конструктор того же класса, либо конструктор базового класса, что сделано с целью обеспечения хорошо спроектированной архитектуры конструкторов. Связанные с этим вопросы обсуждаются в главе 5. Синтаксис инициализатора конструктора требует двоеточия, за которым следует одно из ключевых слов baseили thisдля определения, из какого класса вызывается второй конструктор, за которым следуют параметры, передаваемые этому второму конструктору. Показанный выше конструктор получает три параметра. Однако два из них — nameи salary, присутствуют там только для того, чтобы инициализировать поля базового класса в Employee. Эти параметры относятся на самом деле к классу Employee, а не Manager, поэтому они просто передаются конструктору Employee, с помощью чего делается вызов base(name, salary). Как мы видели раньше, конструктор Employeeбудет просто использовать эти параметры для инициализации полей nameи salary. Наконец, мы передаем параметр bonus, имеющий отношение к классу Manager, и используем для инициализации поля bonus. Второй предоставленный конструктор Managerтакже применяет список инициализации конструктора: public Manager(string name, decimal salary) : this(name salary, 100000M) { } В данном случае задается значение для параметра по умолчанию и затем все передается в конструктор с тремя параметрами. Конечно, в свою очередь, конструктор с тремя параметрами будет вызывать конструктор базового класса для работы с параметрами nameи salary. Можно захотеть узнать, почему не использовался следующий альтернативный способ реализации конструктора с двумя параметрами: public Manager(string name, decimal salary) : base(name, salary) // не очень хорошо { this.bonus = 100000M; } Причина в том, что это приводит к некоторому потенциальному дублированию кода: два конструктора каждый по отдельности инициализируют поле bonus, что может вызывать проблемы в будущем, если понадобится изменить оба конструктора, если в будущей версии Managerизменится способ хранения bonus. Обычно в C#, так же как и VB, стараются по возможности избегать дублирования кода. Таким образом, предыдущая реализация двухпараметрического конструктора считается более предпочтительной. Перезагрузка методовТот факт, что для класса Manager было предоставлено два конструктора, иллюстрирует принцип перезагрузки методов в C#, в соответствии с которым предполагается, что класс имеет более одного метода с одним именем, но эти методы имеют различное число параметров. Мы продемонстрировали перезагрузку конструкторов, но точно такие же принципы применимы ко всем методам.
Когда компилятор встречает вызов перезагруженного метода, он проверяет передаваемые параметры, чтобы определить, какой метод необходимо вызвать. В случае создания объекта менеджера, так как один конструктор получает три параметра, а другой только два, то компилятор прежде всего проверит число параметров. Следовательно, если написать: Manager SomeManager = new Manager("Name", 300000.00M); компилятор будет использовать для создания экземпляра объекта Managerконструктор с двумя параметрами, то есть bonus будет присвоено значение по умолчанию, равное 100000M. Если, с другой стороны, написать: Manager SomeManager = new Manager("Name", 300000.00М, 50000.00М); компилятор использует конструктор с тремя параметрами, поэтому bonus получит указанное значение 50000.00М. При наличии нескольких доступных перезагружаемых версий компилятор не сможет найти подходящую и проинициирует ошибку компиляции. Например, если написать. Manager SomeManager = new Manager(100, 300000.00М, 50000.00М); // неправильно то будет получена ошибка компиляции, так как оба доступных конструктора Managerтребуют строку, а не числовой тип в качестве первого параметра. Компилятор C# может организовать некоторый тип преобразований между различными числовыми типами, которые будут выполняться автоматически, но он не будет автоматически преобразовывать из числового значения в строку. Наконец, отметим, что C# не разрешает методам использовать параметры по умолчанию, как это делает VB. Однако легко получить тот же самый эффект с помощью перезагрузки методов, как это сделано в нашем примере. Обычный способ состоит просто в использовании перезагруженных версий, которые имеют меньше параметров, чтобы подставить значения по умолчанию для оставшихся параметров и затем вызывать другие перезагружаемые версии. Использование классов Employee и ManagerТеперь, когда завершено определение классов Employeeи Manager, напишем код, который их использует. Фактически, если загрузить исходный код этого проекта с web-сайта издательства Wrox press, то можно выяснить, что два эти класса определены как часть стандартного проекта форм Windows, достаточно похожего на пример SampleRoot. В данном случае, однако, основная форма имеет только один элемент управления — поле списка. Мы используем конструктор класса основной формы (класса с именем MainForm) для создания экземпляров объектов Employeeи Manager, а затем выводим данные этих объектов в поле списка. Результат представлен ниже: Код, используемый для создания этого вывода, выглядит следующим образом: public MainForm() { InitializeComponent(); Employee Britney = new Employee("Britney Spearse", 20000.00M); Employee Elton = new Manager("Elton John", 50000.00M); Manager Ginder = new Hanager("Geri Halliwell", 50000.00M, 20000.00M); this.listBox1.Items.Add("Elton's name is $" + Elton.Name); this.listBox1.Items.Add("Elton's salary is $" + Elton.Salary); this.listBox1.Items.Add("Elton's bonus is " + ((Manager)Elton).Bonus); this.listBox1.Items.Add("Elton's monthly payment is $" + Elton.GetMonthlyPayment()); this.listBox1.Items.Add("Elton's Company is " + Employee.CompanyName); this.listBox1.Items.Add("Elton.ToString() : " + Elton.ToString()); this.listBox1.Items.Add("Britney.ToString(): " + Britney.ToString()); this.listBox1.Items.Add("Ginger.ToString(): " + Ginger.ToString()); } Этот код должен быть вполне понятен, так как использует элементы C#, с которыми мы уже знакомы, за исключением одной небольшой странности — один из объектов Managerобозначен ссылкой Employee, а не ссылкой Manager. Мы объясним, как это работает, дальше. Ссылки на производные классыПодробнее рассмотрим класс Manager, на который ссылается переменная, объявленная как ссылка на Employee: Employee Elton = new Manager("Elton John", 50000.00M); Это на самом деле совершенно законный синтаксис C#. Правило вполне простое: если объявлена ссылка на некоторый тип данных В, то этой ссылке разрешается ссылаться на экземпляры В или экземпляры любого производного из В класса. Это работает, так как любой класс, производный из В, должен также реализовать все методы или свойства и т.д., которые реализует класс В. Поэтому в примере выше вызывается Elton.Name, Elton.Salaryи Elton.GetMonthlyPayment(). Тот факт, что Employeeреализует все эти члены, гарантирует, что любой класс, производный из Employee, также будет это делать. Поэтому не имеет значения, указывает ли ссылка на производный класс — мы по-прежнему сможем использовать эту ссылку для вызова любого члена класса, на который определена ссылка, и будем уверены, что этот метод существует в производном классе. С другой стороны, отметим синтаксис, который использовался при вызове свойства Bonusна объекте Elton: ((Manager)Elton).Bonus. В этом случае необходимо явно преобразовать Eltonв ссылку на Manager, так как Bonusне реализовано в Employee. Компилятор знает это и будет создавать ошибку компиляции, если попробовать вызвать Bonusчерез ссылку на Employee. Данная строка кода является на самом деле сокращением записи: Manager ManagerElton = (Manager)Elton; this.listBox1.Items.Add("Elton's bonus is " + ManagerElton.Bonus); Как и в VB, преобразование между типами данных в C# называется преобразованием типов ( casting). Можно заметить в приведенном выше коде, что синтаксис преобразования типов включает размещение имени типа данных в скобках перед именем переменной, преобразование которой собираются выполнить. Конечно, указанный объект должен содержать прежде всего правильный тип данных. Если в этом примере написать: Manager ManagerBritney = (Manager)Britney; то код будет компилироваться правильно, но при его работе будет получена ошибка, так как среда выполнения .NET определит, что Britneyявляется только экземпляром Employee, а не Manager. Ссылкам разрешается ссылаться на экземпляры производных классов, но не на экземпляры базовых классов своего собственного типа. Не разрешается ссылке на Managerссылаться на объект Employee. (Это недопустимо, так как подумайте, что произойдет, если попытаться вызвать свойство Bonus с помощью такой ссылки.) Кстати, совершенно не рассматривались подробности возникновения ошибки во время выполнения. На самом деле C# имеет для такого случая очень развитый механизм, называемый исключениями, который кратко будет показан позже. Так как VB не поддерживает наследование реализации, то не существует прямой параллели в VB для поддержки ссылок, указывающих на объекты производных классов, как в C#. Однако это напоминает VB — можно объявить ссылку на интерфейс, при этом не имеет значения, на какой тип объекта ссылается интерфейс, пока этот объект реализует интерфейс. Если бы классы Employeeи Managerкодировались в VB, можно было вполне сделать так, определяя интерфейс IEmployee, который реализуют оба модуля классов, и затем обращаться к свойствам Employeeчерез этот интерфейс. Массивы объектовВажным достоинством ссылок, способных указывать на экземпляры производных классов, является то, что можно формировать массивы объектных ссылок, где различные объекты массива имеют различные типы. Это аналогично ситуации в Visual Basic, где можно сформировать массив ссылок на интерфейсы и не беспокоиться о том факте, что эти интерфейсные ссылки реализуются совершенно различными классами объектов. Мы не видели еще, как C# работает с массивами, поэтому перепишем код классов Employeeи Manager, чтобы сформировать массив объектных ссылок. Этот пересмотренный код можно также загрузить с web-сайта издательства Wrox Press, как пример EmployeeMaragerWithArrays. Новый код выглядит следующим образом: public MainForm() { InitializeComponent(); Employee Britney = new Employee("Britney Spears", 20000.00M); Employee Elton = new Manager("Elton John", 50000.00M); Manager Ginger = new Manager("Geri Halliwell", 50000.00M, 20000.00M); Employee[] Employees = new Employee[3]; Employees[0] = Britney; Employees[1] = Elton; Employees[2] = Ginger; for (int I = 0; I < 3; I++) { this.listBox1.Items.Add(Employees[I].Name); this.listBox1.Items.Add(Employees[I].ToString()); this.listBox1.Items.Add(""); } } Мы вызываем свойство Nameи метод ToString()каждого элемента массива. Выполнение кода создает следующий результат. Приведенный код показывает, что C# при работе с массивами использует квадратные скобки. Это означает, что в отличие от VB, не существует опасности какой-либо путаницы между массивом и вызовом метода или функции. Синтаксис для объявления массива выглядит так: Employee[] Employees = new Employee[3]; Мы видим, что массив переменных некоторого тип объявляют, помещая квадратные скобки после имени типа. Массив в C# всегда считается ссылочным объектом (даже если его элементы являются простыми типами, как intили double), поэтому на самом деле существует два этапа: объявление ссылки и создание экземпляра массива. Чтобы сделать это понятнее, разделим приведенную выше строку кода следующим образом: Employee[] Employees; Employees = new Employee[3]; He существует разницы между тем, что делается здесь и созданием экземпляров объектов, за исключением того, что используются квадратные скобки для указания, что это массив. Отметим также, что размер массива определяется, когда создается экземпляр объекта, сама ссылка не содержит данных о размере массива — только его размерность. Размерность определяется любым количеством запятых в объявлении массива, поэтому, например, если надо объявить двухмерный, 3×4 массив чисел типа double, можно написать: double [,] DoubleArray = new double[3, 4]; Есть и другие несущественные различия в синтаксисе объявления массивов, но мы будем придерживаться приведенных здесь правил. Когда имеется массив, то значения его элементам присваиваются обычным образом. Отметим, однако, одно различие между C# и VB, состоящее в том, что C# всегда начинает с элемента с индексом 0. В VB имеется возможность изменить его на индекс 1, используя инструкцию Option Base. Также в VB можно определить любую нижнюю границу для любого конкретного массива. Но это свойство не добавляет на самом деле никаких преимуществ и может снизить производительность, так как это означает, что при любом доступе к элементу массива в VB, код должен выполнить дополнительную проверку, чтобы определить, какова нижняя граница массива. C# такое действие не поддерживает. В приведенном выше коде после инициализации элементов массива мы перебираем их в цикле. Необычный синтаксис цикла forбудет скоро рассмотрен. Отметим, что поскольку массив был объявлен как массив объектов Employee, то можно получить доступ только к тем членам каждого объекта, которые определены для класса Employee. Если требуется доступ к свойству Bonusлюбого объекта массива, то необходимо сначала преобразовать соответствующую ссылку в ссылку Manager, что будет означать проверку того, что объект на самом деле является менеджером. Это не трудно сделать, но данный вопрос находится за пределами рассмотрения настоящего приложения. С другой стороны, хотя используются ссылки Employee, мы всегда выбираем правильную версию метода ToString(). Если указанный объект является объектом Manager, то при вызове метода ToString()для этого объекта будет выполняться версия метода ToString(), определенная в классе Manager. В этом состоит достоинство перезагрузки методов в C#. Можно заменить некоторые методы в производном классе и знать, что независимо от того, какой ссылочный тип используется для доступа к этому объекту, для него всегда будет выполняться правильный метод. Цикл forДавайте теперь рассмотрим странный синтаксис цикла for. Этот цикл является эквивалентом C# для следующего кода VB: Integer I For I = 1 То 3 listBox1.Items.Add "Details of the Employee" Next Идея цикла Forв VB состоит в том, что он начинается с инициализации некоторой переменной — управляющей переменной цикла, и каждый раз при проходе цикла что-то добавляется к управляющей переменной, пока она не превысит конечное значение. Это достаточно полезно, но не дает почти никакой гибкости в работе цикла. Хотя можно изменять значение приращения или даже сделать его отрицательным, используя возможности Step, цикл всегда работает с помощью вычислений, и проверка на выход из цикла всегда происходит по достижении переменной некоторого минимального или максимального значения. В C# цикл forобобщает эту концепцию. Базовая идея цикла forв C# следующая. В начале цикла что-то делается, на каждом шаге цикла тоже что-то делается, чтобы перейти к следующей итерации, затем, чтобы определить, когда выходить из цикла, выполняется некоторая проверка. Сравнение версий Visual Basic и C# выглядит следующим образом:
Это может выглядеть как-то неопределенно, но зато дает большую гибкость. Например, в C# вместо добавления некоторой величины к управляющей переменной цикла на каждой итерации можно добавлять какое-то число, которое считывается из файла и которое изменяется на каждой итерации. Проверка не должна быть проверкой значения управляющей переменной цикла, это может быть проверка, например, достижения конца файла. Это позволяет при подходящем выборе начального действия, проверки и действия в конце каждой итерации циклу forэффективно выполнять те же задачи, что и любому из циклов VB — For, Foreach, Doи While, или цикл может вообще работать неким экзотическим образом, для которого нет простого эквивалента в VB. Цикл forв C# действительно представляет полную свободу управления циклом в том виде, какой будет необходим для рассматриваемой задачи.
Давайте вернемся к точному синтаксису. Вспомним, что версия C# для приведенного выше цикла forвыглядит следующим образом: for (int I = 0; I < 3; I++) { this.listBox1.Items.Add(Employees[I].Name); this.listBox1.Items.Add(Employees[I].ToString()); this.listBox1.Items.Add(""); } Как можно видеть, инструкция forполучает три различные элемента внутри скобок. Эти элементы разделяются точкой с запятой: □ Первый элемент является действием, которое выполняется прямо в начале цикла, чтобы инициализировать цикл. В данном случае объявляется и инициализируется управляющая переменная цикла. □ Следующим элементом является условие, определяющее завершение цикла. В данном случае условие состоит в том, что I должно быть меньше 3. Цикл продолжается, пока это условие будет true, и закончится, как только условие станет false. Условие оценивается в начале каждой итерации, чтобы, если оно окажется falseв самом начале, инструкция внутри цикла вообще не выполнялась. □ В третьем элементе находится инструкция, которая выполняется в конце каждой итерации цикла. Циклы Visual Basic всегда работают, увеличивая некоторое число, и это как раз то, что делается в данном случае. Синтаксис выглядит незнакомым, но после привыкания к нему, можно использовать цикл forочень эффективно. Например, предположим, что необходимо вывести все целые степени 2, которые меньше 4000, в окне списка. Запишем следующий код: for (int I = 2; I < 4000; I *= 2) listBox1.Items.Add(I.ToString()); Такой результат можно получить и в VB, но немного труднее. Для этого конкретного цикла в VB лучше воспользоваться циклом while. Другие свойства C#Мы закончили рассмотрение примеров. Оставшаяся часть приложения кратко рассматривает несколько свойств C#, о которых необходимо знать при выполнении перехода от VB к C#, и которые еще не рассматривались, в частности, некоторые из концепций C#, связанные с типами данных и операторами. Типы данныхКак указывалось ранее, доступные в C# типы данных отличаются от типов данных, доступных в Visual Basic, лишь деталями. Но не только это, все типы данных в C# имеют свойства, которые обычно связываются с объектами. Например, как мы видели, каждый тип данных, даже простые типы, такие как intи float, поддерживает вызов методов (кстати, это свойство не вызывает никакой потери производительности). Хотя типы данных, доступные в C#, слегка отличаются от типов данных в VB, все же большинство типов данных, знакомые из VB, имеют непосредственные эквиваленты в C#. Например, вместо Doubleиз VB, C# имеет double, вместо Dateиз VB , C# имеет базовый класс .NET DateTime, который реализует огромное число методов и свойств, позволяющих извлекать или задавать даты с помощью различных форматов. Одним исключением является Variant, для которого нет прямого эквивалента в C#. Тип данных Variantв VB является очень общим типом данных, который в некоторой степени существует только, чтобы поддерживать языки сценариев, которые не знают никаких других типов данных. Философия C#, однако, состоит в том, что язык является строго типизированным. Главная идея: если в каждом месте программы явно указывать используемый тип данных, то будет исключен один из основных источников ошибок времени выполнения. В связи с этим тип данных Variantв действительности не соответствует C#. Но все равно существуют ситуации, в которых необходимо ссылаться на переменную, не указывая тип этой переменной, и для этих случаев C# имеет тип данных object. objectв C# очень похож на Objectв VB. Однако в VB Objectссылается конкретно на объект COM и поэтому может использоваться только для ссылок на объекты, которые в терминологии VB означают ссылочные типы данных. Нельзя, например, использовать объектную ссылку для ссылки на Integerили на Single. В C#, напротив, объекты могут использоваться для ссылки на любой тип данных .NET, и так как в C# все типы данных являются типами данных .NET, это означает, что вы в праве преобразовать что угодно в object, включая int, floatи все предопределенные типы данных. В этом смысле objectв C# выполняет роль, аналогичную Variantв VB. Типы данных значений и ссылочные типы данныхВ Visual Basic существует четкое различие между типами данных значений и ссылочными типами данных. Типы данных значений включают большинство предопределенных типов данных: Integer, Single, Doubleи даже Variant(хотя строго говоря Variantможет содержать также ссылку). Ссылочными типами данных являются любые объекты, включая определяемые модули классов и объекты ActiveX. Как можно было заметить из примеров в этом приложении, C# также делает различие между типами данных значений и ссылочными типами данных. Однако C# допускает больше гибкости, позволяя при определении класса найти, что этот класс будет типом данных значений. Это делается с помощью объявления класса структурой (struct). В той степени, насколько это касается C#, структура является по сути специальным типом класса, который представляется как значение, а не как ссылка. Накладные расходы, связанные с созданием экземпляров структур и их разрушением при завершении с ними работы меньше, чем связанные с созданием экземпляров и разрушением классов. Однако C# ограничивает свойства структур. В частности, нельзя выводить классы или другие структуры из структур. Причина этого состоит в том, что структуры предназначены для использования в качестве динамичных, простых объектов, для которых наследование в действительности не подходит. Фактически все предопределенные классы в C# — int, long, float, doubleявляются на самом деле структурами .NET, вот почему на них можно вызывать такие методы, как ToString(). Тип данных stringявляется, однако, ссылочным типом и поэтому в действительности является классом. ОператорыНеобходимо сказать несколько слов об операторах в C#, так как они действуют несколько иным образом по сравнению с операторами VB, и это может внести путаницу, если программист привык работать с VB. В VB существует на самом деле два типа операторов: □ Оператор присваивания =, который присваивает значения переменным. □ Все другие операторы: +, -, *и /, каждый из которых возвращает какое-то значение. Здесь существует важное различие в том, что ни один из операторов, кроме =, не имеет никакого эффекта в смысле изменения какого-либо значения. Со своей стороны, =присваивает значение, но ничего не возвращает. Не существует оператора, который делает и то и другое. В C# такого разделения просто не существует. Правило в C# гласит, что все операторы возвращают значение, а некоторые операторы имеют также дополнительный побочный эффект, присваивая некоторое значение переменной. Фактически мы уже видели пример этого, когда рассматривали оператор сложения-присваивания +=: int А = 5, В = 15; А += В; // выполняет арифметическую операцию // и присваивает А результат (20) Таким образом, +=возвращает, а также присваивает значение. Он возвращает новое значение, которое было присвоено. В связи с этим можно на самом деле написать: int А = 5, B = 15; int C = (А+=В); Это в результате приведет в тому, что и А, и Сбудет присвоено значение 20. Оператор присваивания =также возвращает значение, которое было присвоено переменной с левой стороны выражения. Это означает, что можно записать код следующим образом: С = (А = В); Этот код задает Аравным значению В, а затем то же самое значение присваивает С. Можно также написать эту инструкцию более просто: С = А = В; Обычное использование такого синтаксиса состоит в вычислении некоторого условия внутри инструкции ifи одновременном задании результата этого условия в виде переменой типа bool(эквивалент в C# для Booleanиз VB), чтобы можно было использовать это значение позже: // предположим, что X и Y — переменные, которые были инициализированы. bool В; if (В = (X==Y)) DoSomething(); Этот код выглядит пугающим на первый взгляд, но он вполне логичен. Давайте разберем его. Прежде всего компьютер проверяет условие X == Y. В зависимости от того, содержат ли Xи Yодинаковые данные, оно возвратит trueили falseи это значение будет присвоено переменной В. Однако поскольку оператор присваивания также возвращает значение, которое было только что присвоено все выражение В = (X==Y)также будет возвращать то же самое значение ( trueили false). Это возвращаемое значение затем используется предложением ifдля определения, нужно ли выполнять условную инструкцию DoSometning(). В результате этого кода условие X == Yпроверяется для выяснения, должны ли выполняться условные инструкции, и в то же самое время результаты этой проверки сохраняются в переменной В. Тернарный операторВ этом приложении нет места для обсуждения всех доступных в C# операторов. Они подробно рассмотрены в главах 3-6. Однако упомянем тернарный оператор (известный также как условный оператор), так как он имеет очень необычный синтаксис. Тернарный оператор формируется из двух символов — ?и :. Он имеет три параметра и на самом деле эквивалентен инструкции Ifв VB. Синтаксически используется следующие образом: // В, X и Y являются некоторыми переменными или выражениями. // В является Boolean. B ? X : Y и работает так: оценивается первое выражение, которое расположено перед символом ?, если оно оценивается как true, то возвращается результат второго выражение но если оно оценивается как false, то вместо этого возвращается результат третьего выражения. Это предоставляет предельно компактный синтаксис для условного задания значения переменной. Например, можно написать: int Z = (Х==Y) ? 5 : 8; что будет иметь такой же результат, как и следующие вычисления: int Z; if (X==Y) Z = 5; else Z = 8; ЗаключениеВ этом приложении было представлено краткое введение в C# с точки зрения сравнения его с Visual Basic. Мы обнаружили довольно мало различий в синтаксисе. В целом синтаксис C# позволяет выразить большинство инструкций более компактным образом. Также можно заметить большое сходство между языками, например в их использовании классов (или модулей классов в VB), типов данных значений, ссылочных типов данных и многих синтаксических структур. Однако C# поддерживает также многие мощные свойства, в частности связанные с наследованием и классическим объектно-ориентированным программированием, которые недоступны в VB. Выполнение перехода от VВ к C# требует специальной подготовки, но стоит того, так как методология C# позволяет не только кодировать приложения, которые можно сделать в VB но также множество других приложений которые было бы трудно или невозможно создать в VB хорошо структурированными и легко поддерживаемыми. При использовании C# можно также получить дополнительный бонус среды выполнения .NET и всех связанных с этим преимуществ. |
|
||||||||||||
Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх | ||||||||||||||
|