Введение в теорию программирования. Объектно-ориентированный подход

         

Объектно-ориентированный подход к программированию


Рассмотрим особенности объектно-ориентированного подхода к программированию в сравнении с функциональным подходом. Напомним, что классификация подходов к программированию была построена нами во вступительной лекции.

Важнейшим шагом на пути к совершенствованию языков программирования стало появление объектно-ориентированного подхода к программированию (или, сокращенно, ООП) и соответствующего класса языков. Именно исследование теории и практики проектирования и реализации программных систем по принципам ООП и является основной целью второй части данного курса.

При объектно-ориентированном подходе программа представляет собой описание объектов, их свойств (или атрибутов), совокупностей (или классов), отношений между ними, способов их взаимодействия и операций над объектами (или методов).

Несомненным преимуществом данного подхода является концептуальная близость к предметной области произвольной структуры и назначения. Механизм наследования атрибутов и методов позволяет строить производные понятия на основе базовых и таким образом создавать модель сколь угодно сложной предметной области с заданными свойствами.

Еще одним теоретически интересным и практически важным свойством объектно-ориентированного подхода является поддержка механизма обработки событий, которые изменяют атрибуты объектов и моделируют их взаимодействие в предметной области.

Перемещаясь по иерархии классов от общих понятий предметной области к более конкретным (или от более сложных – к более простым) и наоборот, программист получает возможность изменять степень абстрактности или конкретности взгляда на моделируемый им реальный мир.

Использование ранее разработанных (возможно, другими коллективами программистов) библиотек объектов и методов позволяет значительно сэкономить трудозатраты при производстве программного обеспечения, в особенности, типичного.

Объекты, классы и методы могут быть полиморфными, что делает реализованное программное обеспечение более гибким и универсальным.

Сложность адекватной (непротиворечивой и полной) формализации объектной теории порождает трудности тестирования и верификации созданного программного обеспечения.
Пожалуй, это обстоятельство является одним из самых существенных недостатков объектно-ориентированного подхода к программированию.

Наиболее известным примером объектно-ориентированного языка программирования является язык C++, развившийся из императивного языка С. Его прямым потомком и логическим продолжением является язык С#, который изучается в данном курсе. Другие примеры объектно-ориентированных языков программирования: Visual Basic, Java, Eiffel, Oberon.

Переход от структурно-процедурного подхода к объектно-ориентированному программированию, подобно переходу от низкоуровневых языков программирования к языкам высокого уровня, требует значительных затрат на обучение. Естественно, что платой за это является повышение производительности труда программистов при проектировании и реализации программного обеспечения. Другое преимущество ООП перед императивным подходом – более высокий процент повторного использования уже разработанного программного кода.

При этом, в отличие от предыдущих подходов к программированию, объектно-ориентированный подход требует глубокого понимания основных принципов, или, иначе, концепций, на которых он базируется. К числу основополагающих понятий ООП обычно относят абстракцию данных, наследование, инкапсуляцию и полиморфизм.

Зачастую в практических и учебных курсах по программированию слушатели не имеют четкого математического основания для формирования достаточно полного и ясного представления об основах ООП. Преимущество предлагаемого курса заключается в том, что уже изученные в первой части курса разделы computer science (например, ламбда-исчисление и комбинаторная логика) позволяют сформировать глубокое и точное понимание фундаментальных понятий объектно-ориентированного программирования. В частности, понятие абстракции – основной операции ламбда-исчисления – для нас является уже хорошо знакомым.

Поясним качественно фундаментальные принципы ООП. Наследование конкретных атрибутов объектов и функций оперирования объектами основано на иерархии.


Инкапсуляция означает "сокрытие" свойств и методов внутри объекта. Полиморфизм, как и в функциональном программировании, понимается как наличие функций с возможностью обработки данных переменного типа.

Рассмотрим более подробно такой фундаментальный принцип объектно-ориентированного подхода к программированию как абстракция.

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

Важнейшей операцией, которая была исследована нами в первой части курса, является операция вычисления значения выражения или команды, т.е. операция означивания (в частности, функция вычисления значения явно использовалась при построении семантики языка программирования). В этой связи важно установить, что является значением абстракции. Будем считать, что значение функции или переменной может быть присвоено абстракции и является значением последней.

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

Как мы уже отмечали, концепция абстракции в объектно-ориентированном программировании адекватно моделируется посредством ламбда-исчисления. Точнее говоря, операция абстракции в полной мере является моделью одноименного понятия ООП.



С точки зрения языков программирования понятие наследования означает применимость всех или лишь некоторых свойств и/или методов базового (или родительского) класса для всех классов, производных от него. Кроме того, сохранение свойств и/или методов базового класса должно обеспечиваться и для всех конкретизаций (т.е. конкретных объектов) любого производного класса.

В математике концепцию наследования принято моделировать, например, отношением частичного порядка (которое представляет собой вид иерархии). Концепция наследования адекватно формализуется математически посредством одной из следующих нотаций:

фреймовой нотации Руссопулоса (названной так по имени своего создателя, N.D. Roussopulos); диаграмм Хассе (получивших название по имени ученого, который впервые предложил этот способ наглядного представления наследования, H. Hasse).

Рассмотрим пример программы на языке программирования C#, иллюстрирующий концепцию наследования:

class A { // базовый класс int a; public A() {...} public void F() {...} }

// подкласс (наследует свойства //класса A, расширяет класс A) class B:A { int b; public B() {...} public void G() {...} }

Пример представляет собой описание базового класса A и производного от него класса B.

Класс A содержит целочисленный атрибут (т.е. переменную) a, а также два метода (т.е. функции), A() и F(). Класс B содержит целочисленный атрибут (т.е. переменную) b, а также два метода (т.е. функции) B() и G().

Двоеточие B:A в описании класса B означает наследование.

Отметим, что выше был рассмотрен простейший случай наследования, а именно, единичное наследование. Язык программирования C# позволяет реализовать механизмы, поддерживающие и более сложный случай наследования – множественное наследование.

Рассмотрим более подробно особенности наследования, которые реализует данный пример программы на языке C#.

Производный класс B наследует от базового класса А свойство a и метод F(). При этом к классу В добавляются собственные свойство b и метод G().

Заметим, что в отношении операции наследования справедливы следующие ограничения:



конструкторы (т.е. функции создания и инициализации классов) не наследуются; в языке C# существует возможность замещения наследуемых методов (этот языковой аспект будет рассмотрен более подробно в ходе дальнейших лекций).

Как уже отмечалось, наиболее простым случаем наследования является так называемое единичное наследование. При таком наследовании производный класс (или, иначе, подкласс) может наследовать свойства только одного базового класса. Однако при этом производный класс может реализовывать множественные интерфейсы (т.е. использовать описания объектов и методов других классов, напрямую минуя механизм наследования).

Один класс языка программирования C# может наследовать лишь свойства другого класса (но не структуры – типа данных, аналогичного кортежу языка программирования SML).

Структура не может наследовать свойства другого типа данных, однако может при этом реализовывать как один, так и несколько интерфейсов.

Подкласс с неявным базовым классом наследует свойства наиболее абстрактного класса, известного под названием "объект" (object).

Еще одним фундаментальным компонентом концепции объектно-ориентированного программирования является понятие инкапсуляции.

Неформально говоря, под инкапсуляцией понимается возможность доступа к объекту и манипулирования им исключительно посредством предоставляемых именно этим объектом свойств и методов.

Таким образом, свойствами объекта (безразлично, являются ли они явно описанными или производными от других объектов) возможно оперировать исключительно посредством методов, которые содержатся в описании данного объекта (или родительских по отношению к нему объектов, при условии, что эти методы унаследованы).

Инкапсуляция является весьма важным свойством, поскольку обеспечивает определенную (а точнее, определяемую программистом) степень доступности объекта.

Хотя инкапсуляция как таковая является фундаментальным свойством ООП, степень инкапсуляции при наследовании может варьироваться в зависимости от типа области видимости объекта, который определяется модификатором видимости.





Так, используемый в предыдущем примере модификатор видимости public обеспечивает доступность свойств и методов объекта из произвольного места программы.

К основным свойствам инкапсуляции относятся следующие возможности:

совместное хранение данных и функций (т.е. свойств и методов) внутри объекта;сокрытие внутренней информации от пользователя (что обеспечивает большую безопасность приложения);изоляция пользователя от особенностей реализации (что обеспечивает независимость от машины и потенциально дружественный интерфейс приложений).

Как нам уже известно, важная позитивная особенность языка программирования SML заключается в том, что в нем поддерживается так называемая полиморфная типизация.

В объектно-ориентированном программировании под полиморфизмом понимается возможность оперировать объектами, не обладая точным знанием их типов.

Рассмотрим пример простейшей полиморфной функции:

void Poly(object o) { Console.WriteLine(o.ToString()); }

Данная функция реализует отображение на экране объекта (метод Console.WriteLine) с предварительным преобразованием его к строковому типу (метод ToString()) .

Все приведенные ниже варианты вызова функции:

Poly(25); Poly("John Smith"); Poly(3.141592536m); Poly(new Point(12,45));

успешно пройдут компиляцию и завершатся выдачей корректного результата.

Для более подробного самостоятельного ознакомления с тематикой лекции рекомендуется следующий список источников: [22, 38, 51, 53, 61, 63, 75].


Платформа.NET и ее применение для объектно-ориентированного подхода к программированию


Исследуем особенности приложения идеологии .NET для проектирования и реализации объектно-ориентированных программ.

Корпорацией Microsoft предложен новаторский компонентно-ориентированный подход к программированию, который является развитием объектно-ориентированного направления. Согласно этому подходу, интеграция объектов (возможно, гетерогенной природы) производится на основе интерфейсов, представляющих эти объекты (или фрагменты программ) как независимые компоненты. Такой подход существенно облегчает написание и взаимодействие программных "молекул"-компонент в гетерогенной среде проектирования и реализации. Стандартизируется хранение и повторное использование компонент программного проекта в условиях распределенной сетевой среды вычислений, где различные компьютеры и пользователи обмениваются информацией, например, взаимодействуя в рамках исследовательского или бизнес-проекта.

Существенным преимуществом следует считать и возможность практической реализации принципа "всякая сущность является объектом" в гетерогенной программной среде. Во многом это стало возможным благодаря усовершенствованной, обобщенной системе типизации Common Type System, или CTS, которая будет подробнее рассмотрена в одной из следующих лекций.

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

Новый подход к интеграции компонент приложений в среде вычислений Internet (или так называемые веб-сервисы) дает возможность ускоренного создания приложений для широкого круга пользователей.

Универсальный интерфейс .NET Framework обеспечивает интегрированное проектирование и реализацию компонентов приложений, разработанных согласно различным подходам к программированию.

Говоря о .NET как о технологической платформе, нельзя не отметить тот факт, что она обеспечивает одновременную поддержку проектирования и реализации программного обеспечения с использованием различных языков программирования.
При этом поддерживаются десятки языков программирования, начиная от самых первых (в частности, COBOL и FORTRAN) и заканчивая современными (например, C# и Visual Basic). Ранние языки программирования до сих пор активно используются, в частности, для обеспечения совместимости с ранее созданными приложениями, критичными для бизнеса (скажем, COBOL весьма широко применялся для создания прикладных программ, поддерживающих финансовую деятельность).

Применение технологии веб-сервисов – это не просто дань моде на Internet, а реальная (и, пожалуй, наиболее приемлемая) возможность обеспечения масштабируемости и интероперабельности приложений. Под масштабируемостью понимают возможность плавного роста времени ответа программной системы на запрос с ростом числа одновременно работающих пользователей; в случае веб-сервисов масштабируемость реализуется посредством распределения вычислительных ресурсов между сервером, на котором выполняется прикладная программа (или хранятся данные) и компьютером пользователя.

Под интероперабельностью понимается возможность интегрированной обработки гетерогенных данных, поступающих от разнородных прикладных программ. Именно благодаря интероперабельности возможна унификация взаимодействия пользователей через приложение с операционной системой на основе специализированного интерфейса прикладных программ, или API-интерфейса (Application Programming Interface).

Следует отметить и то обстоятельство, что новая технология .NET не только востребована мировой общественностью, но и официально признана, что отражено в соответствующих стандартах ECMA (European Computer Manufacturers Association).

Теперь рассмотрим инструментальные возможности .NET как средства проектирования и реализации программного обеспечения, то есть, собственно, программирования в широком смысле этого слова.

Прежде всего, необходимо отметить поддержку многоязыковой среды разработки приложений CLR (Common Language Runtime).

Эта возможность появилась благодаря универсальному межъязыковому интерфейсу Common Language Infrastructure, или CLI, который поддерживает разработку программных компонентов на различных языках программирования.



При этом несомненным преимуществом для программистов является то, что они могут разрабатывать (или дорабатывать) программное обеспечение на наиболее подходящем языке программирования. Здесь следует учитывать характер задачи (скажем, рекурсию или символьную обработку более естественно реализовать на языке функционального программирования, а формализацию структуры предметной области – на объектно-ориентированном языке). Кроме того, необходимо принимать во внимание опыт работы программистов в команде разработчиков и тот язык программирования, на котором изначально создавалась система.

Отметим еще два существенных обстоятельства. Во-первых, основные сервисные возможности для разработчиков, которые предоставляет среда .NET (отладка, анализ кода и т. д.) не зависят от конкретного языка программирования, и, следовательно, программистам нет необходимости заново постигать особенности среды разработки, если требуется перейти с одного языка на другой. Во-вторых, несмотря на то, что еще не все языки программирования поддерживаются .NET, существует возможность самостоятельной разработки транслятора для любого языка программирования, причем это не вызывает трудностей даже у программистов, практически не имеющих профессиональной подготовки в области разработки компиляторов.



Среда выполнения CLR реализует управление памятью, типами данных, межъязыковым взаимодействием, развертыванием (deployment) приложений.


Рис. 14.1.  Архитектурная схема .NET Framework и Visual Studio.NET.

Существенным преимуществом конструктивного решения .NET является компонентно-ориентированный подход к проектированию и реализации программного обеспечения. Суть подхода состоит в принципиальной возможности создания независимых составляющих программного обеспечения с унифицированной интерфейсной частью для многократного повторного и распределенного использования. При этом продуктивность решения обусловлена многоязычностью интегрируемых программных проектов (концепция .NET потенциально поддерживает произвольный язык программирования, в числе наиболее известных языков – C#, Visual Basic, C++ и др.).

В ходе компиляции программа на .NET-совместимом языке программирования трансформируется в соответствии с заранее заданной обобщенной спецификацией языка Common Type System (CTS). Система типов CTS полностью описывает все типы данных, поддерживаемые средой выполнения, определяет их взаимосвязи и хранит их отображения в системе типов .NET.

Под Common Language Specification (или CLS) понимается набор правил, определяющих подмножество обобщенных типов данных, в отношении которых гарантируется, что они безопасны при использовании во всех языках .NET.

Интерфейсы реализуются посредством форм Windows и ASP.NET для веб-приложений.

В ходе выполнения процедуры трансляции исходный текст программы (написанный на SML, C#, Visual Basic, C++ или любом другом языке программирования, который поддерживается .NET) преобразуется компилятором в так называемую сборку (assembly) и сохраняется в виде файла динамически присоединяемой библиотеки (Dynamically Linked Library, DLL) или исполняемого файла (Executable, EXE).


Рис. 14.2.  Схема компиляции Common Language Runtime.

Естественно, что для каждого компилятора (будь то компилятор языка C#, csc.exe или Visual Basic, vbc.exe) средой времени выполнения производится необходимое отображение используемых типов в типы CTS, а программного кода – в код "абстрактной машины" .NET – MSIL (Microsoft Intermediate Language).



В итоге программный проект формируется в виде сборки – самодостаточного компонента для развертывания, тиражирования и повторного использования. Сборка идентифицируется цифровой подписью автора и уникальным номером версии.

Рассмотрим достаточно обобщенный пример трансляции многокомпонентного гетерогенного программного проекта под управлением Microsoft .NET (см. рис. 14.3).

Предположим, что компоненты проекта написаны на трех языках программирования: уже знакомом нам языке SML, изучаемом языке C#, а также языке С++, который характеризуется возможностью создания потенциально небезопасного кода (в частности, динамического распределения памяти).

Исходные тексты компонент проекта транслируются, соответственно, компиляторами с языков SML, C# и C++ в унифицированный MSIL-код и сохраняются в файлах в виде сборок.

В ходе компоновки и выполнения программного проекта Just-In-Time (JIT) компилятор среды CLR производит выполнение проекта с "ленивым" (по мере необходимости) означиванием оттранслированного промежуточного кода сборок.


Рис. 14.3.  Схема выполнения CLR.

Существенно, что потенциально небезопасный код на языке C++ принципиально невыполним собственно JIT-компилятором, но исполняется посредством сервисов операционной системы. Ответственность за работоспособность программы и безопасность кода в этом случае лежит уже не на среде проектирования и разработки программного обеспечения .NET, а на программисте-разработчике.

Существенным позитивным отличием Microsoft .NET от существующих аналогов на современном рынке программного обеспечения является универсальная система типизации.

В ходе компиляции программа на .NET-совместимом языке программирования трансформируется в соответствии с заранее заданной Common Type System (CTS) обобщенной спецификацией языка. Система типов CTS полностью описывает все типы данных, поддерживаемые средой выполнения, определяет их взаимосвязи и хранит их отображения в систему типов .NET.


Рис. 14.4.  Универсальная система типизации (UTS).



Система типизации Microsoft . NET представляет собой частично упорядоченное множество, которое на качественном уровне может пониматься как ISA-иерархия (аббревиатура ISA происходит от английских слов "is a", которые означают "является одним из").

Так, например, высказывание STUDENT ISA PERSON означает, что тип STUDENT является подтипом типа PERSON (здесь вполне уместна аналогия с множествами и вполне точна аналогия с доменами).

Таким образом, система типов Microsoft .NET образует иерархию с возрастанием общности снизу вверх (см. рис. 14.4), в которой явно выделяются две большие группы типов, а именно, типы-ссылки и типы-значения. Различие между последними определяется особенностями вызова в процедурах: по имени или по значению (call-by-name, CBN) и по ссылке (call-by-reference, CBR).

Заметим также, что система типизации Microsoft .NET помимо развитой иерархии предопределенных типов позволяет пользователю создавать собственные типы (как типы-ссылки, так и типы-значения) на основе уже существующих.

Изложение важнейших аспектов Microsoft .NET было бы неполным, если бы мы не упомянули о таком существенном архитектурном принципе как веб-сервисы.

Значение веб-сервисов заключается в распределении возможностей разработанных прикладных систем по каналам Internet.


Рис. 14.5.  Веб-серверы в .NET.

Заметим, что центральным блоком в схеме (рис. 14.5) является .NET Framework, который можно рассматривать как библиотеку базовых объектов и операций над ними.

В качестве среды разработки прикладных систем целесообразно использовать Microsoft Visual Studio .NET, предоставляющую целый комплекс развитых средств создания, редактирования и отладки программного кода на различных языках программирования. В случае несложных задач можно ограничиться примитивными редакторами текста программ, подобных Notepad.

Интерфейсная часть прикладной программной системы в Internet-архитектуре представлена так называемыми веб-формами, предназначенными для ввода и вывода данных в унифицированном формате.



В качестве языка реализации может использоваться язык гипертекстовой разметки HTML (HyperText Markup Language). Взаимодействие между клиентом и приложением в простейшем случае осуществляется с использованием традиционного Internet-протокола передачи данных HTTP (HyperText Transfer Protocol).

Структурированные данные хранятся в формате XML (вариант HTML с более строгим синтаксисом).

Заметим, что технология веб-сервисов, реализованная Microsoft, допускает интеграцию с компонентами независимых производителей.

Попытаемся сформулировать определение понятия "веб-сервис" (или, иначе, "веб-служба").

Под веб-сервисами обычно понимают программируемые компоненты прикладных программных систем, которые доступны для клиента (пользователя) посредством стандартных протоколов, применяемых для работы в Internet-среде.

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

Смысл использования веб-сервисов состоит в возможности распределения функций разработанных прикладных систем по глобальной сети.

Для реализации этой задачи веб-сервисы организуются на проверенных временем, традиционных, а также развивающихся стандартах взаимодействия приложений в Internet, а именно:

HTTP – стандартный протокол обмена гипертекстовыми документами в Internet с возможностью передачи данных посредством веб-форм;XML – формат хранения структурированных данных с возможностью обмена ими по Internet-каналам;SOAP – стандартный протокол взаимодействия компонент (глобально) распределенного приложения (Simple Object Access Protocol);UDDI – стандарт интеграции приложений (Universal Description, Discovery and Integration);WSDL – универсальный язык описания веб-сервисов (Web Service Description Language),

а также целом ряде других менее употребительных протоколов.



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

Центральной концепцией подхода (и это очевидно уже из названия) является понятие компонента.

Под компонентом в дальнейшем будем понимать независимый модуль программного обеспечения, который можно использовать повторно, а также тиражировать.

В отличие от "традиционных" объектов ООП компоненты обладают следующими характеристическими свойствами:

в целом компонент обладает более высоким уровнем абстракции по сравнению с объектом (под последним понимается конструкция уровня языка программирования);

компоненты могут содержать в своем составе множественные классы;

компоненты с точки зрения пользователя являются инвариантами по отношению к тому языку программирования, на котором они реализованы.

Таким образом, оказывается, что в общем случае разработчик и пользователь компонента могут быть территориально разделены и имеют возможность использовать различные языки программирования в рамках единой среды разработки приложений Microsoft .NET.

Заметим, что попытки построения компонентных программных систем предпринимались и рядом других разработчиков программного обеспечения (в частности, технология JavaBeans производства Sun Microsystems), а также международных ассоциаций, объединяющих усилия исследователей и практиков в области объектного программирования (например, стандарт брокеров объектных запросов CORBA организации Object Management Group).

В основе таких попыток лежали варианты объектных моделей. Один из подобных вариантов, детально проработанный с математической точки зрения,– модель двухуровневой концептуализации, которую нам предстоит рассмотреть в ходе данного курса. Пока же лишь кратко охарактеризуем основные отличительные особенности наиболее известных из представленных на рынке современного программного обеспечения объектных моделей.

Прежде всего, рассмотрим компонентную модель Microsoft, которая обычно обозначается в литературе аббревиатурой COM (Component Object Model).



Компонентная объектная модель COM является основным стандартом Microsoft для компонентного проектирования и реализации программного обеспечения. На сегодня это самая развитая, и, пожалуй, самая удачная в практическом плане модель, которая обеспечивает возможность инициализации и использования компонентов как внутри одного процесса, так и между процессами или между компьютерами независимо от языка реализации. COM-модель поддерживается в идеологии .NET для целого ряда языков программирования (C#, SML, Visual Basic, C++ и др.), является основой для ActiveX, OLE, а также для многих других технологий Microsoft.

В отличие от СОМ, модель Java Beans, базовый стандарт Sun Microsystems для компонент, оказывается зависимой от языка реализации.

Исследовав основные особенности объектно-ориентированного и компонентного подходов к проектированию и реализации программного обеспечения, произведем краткий сравнительный анализ этих особенностей и подходов в целом.

Прежде всего, перечислим основополагающие понятия, характеризующие каждый из подходов. Затем сопоставим эти подходы друг с другом с целью поиска аналогий между ними.

В объектно-ориентированном подходе ключевыми следует считать, в частности, понятия класса и интерфейса. Заметим, что в компонентно-ориентированном подходе эти понятия также являются системообразующими.

При этом под классом понимается базовая сущность, определяемая как совокупность элементов.

Под интерфейсом понимается набор семантически связанных абстрактных элементов. Для компонентно-ориентированного подхода понятие интерфейса имеет первостепенное значение, поскольку исключительно посредством этого механизма клиент в архитектуре с моделью COM может непосредственно осуществлять взаимодействие с COM-классом. Заметим, что интерфейсы повышают безопасность кода, так как взаимодействие с объектом происходит не непосредственно, а через указатель (ссылку). Понятия свойства (как атрибута объекта) и метода (как операции над объектом), как и механизма событий (соотнесений над объектами предметной области), свойственны обоим подходам.



Принципиально новым является наличие в COM-модели сборок – самодостаточных единиц информации для инсталляции и развертывания программных продуктов.

В целом компонентный подход является более удобным с практической точки зрения, хотя механизмы, реализованные в нем, принципиально сравнимы с возможностями ООП.

Несмотря на перечисленные выше инновации в области теории, технологии и практической реализации, в силу масштабности идеологии и новизны исследуемой проблематики, подход .NET не лишен отдельных недостатков, большинство из которых, по-видимому, носит временный характер. Перечислим, по нашему мнению, наиболее существенные из них.

Во-первых, разработчики отмечают достаточно высокие требования к аппаратному обеспечению (в частности, объем оперативной памяти должен быть не менее 256 мегабайт, свободный объем жесткого диска для работы с Microsoft Visual Studio .NET – не менее 10 гигабайт).

Кроме того, некоммерческие версии программных продуктов Microsoft, которые зачастую предоставляют новые существенные возможности, в недостаточной степени устойчивы в работе; документация по ряду новых функций программного обеспечения представлена не в полном объеме.

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

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

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

Несомненное качественное превосходство над существующими средствами автоматизированного проектирования и быстрой реализации прикладного программного обеспечения достигается за счет следующих основных факторов:



интероперабельность и межъязыковое взаимодействие;многоуровневая, гибкая и надежная политика безопасности; интеграция с технологией веб-сервисов;упрощение процедуры развертывания и использования создаваемого программного обеспечения.

Несмотря на некоторую незавершенность решения для широкого коммерческого использования в силу концептуальной новизны и грандиозности проекта, подход .NET, безусловно, оказывает значительное влияние на коммерческую индустрию программирования в целом и способствует радикальному совершенствованию отрасли.

Для более подробного самостоятельного ознакомления с тематикой лекции рекомендуется следующий список источников: [25, 41, 60, 64, 82].


Основные понятия языка программирования C#


Прежде чем перейти непосредственно к исследованию конструктивных особенностей языка программирования C#, рассмотрим ход его развития.

История основной ветви языков программирования, которая привела к появлению C#, восходит к 60-м годам, а именно, ко времени возникновения языка B. Последний является типичным представителем ранних императивных языков программирования. Язык B был придуман в 1963 году творческим коллективом разработчиков, основным создателем языка принято считать К. Томпсона из Технологического университета Массачусетса (Ken Thompson, MIT). Основной целью разработки языка была реализация операционной системы UNIX. Уже существовавший язык PL/I, применявшийся в то время для мэйнфреймов производства компании IBM, был достаточно громоздким и меньше подходил для поставленной задачи, чем новое, оригинальное решение ученых-практиков.

Следующим шагом в "алфавите" языков программирования, ведущем к языку C#, стал язык C, который был изобретен на основе языка B в 1972 году. Авторами нового языка программирования стали К.Томпсон и Д.Ритчи (Dennis Ritchie), которые работали в исследовательской лаборатории компании AT&T (AT&T Bell Telephone Laboratories). В варианте C язык B расширился за счет явного использования типов, структур и ряда новых операций. Дальнейшее развитие языка происходило в той же организации. И снова примерно через 10 лет, в 1984 году, Б. Страуструп (Bjarne Stroustrup, Bell Labs) выступил с проектом языка С++ – ООП-расширения языка C, в котором вводится понятие класса как объекта данных.

Заметим, что название C++ для нового языка предложил Р.Маскитти (Rics Mascitti, Bell Labs).

Наконец, уже в 2000 году, то есть более чем через 15 лет, корпорация Microsoft выпустила в свет C++ нового поколения под названием C# ("Си шарп"), основным постулатом которого является высказывание: "всякая сущность есть объект". Язык основан на строгой компонентной архитектуре и реализует передовые механизмы обеспечения безопасности кода.


Как уже отмечалось в ходе лекции, язык программирования C# объединил лучшие черты целого ряда предшественников. Кроме упомянутой ранее ветви языков B-C-C++, необходимо указать еще несколько знаковых для настоящего времени языков программирования, а именно, Java и Visual Basic.

Несмотря на весьма существенные различия между компонентной объектной моделью COM (основного стандарта Microsoft для компонентного проектирования и реализации программного обеспечения) и моделью Java Beans, базовым стандартом Sun Microsystems для компонент (зависимой от языка реализации), язык программирования C# имеет довольно много общего с языком Java. Естественно, немало черт язык программирования C# унаследовал и от своего предшественника, созданного корпорацией Microsoft, языка Visual Basic.

Как уже отмечалось, язык программирования C# основан на строгой компонентной архитектуре и реализует передовые механизмы обеспечения безопасности кода.

Перечислим наиболее характерные черты сходства языков программирования C# и Java. Прежде всего, оба языка относятся к категории объектно-ориентированных и предполагают единственность наследования. Другими важными особенностями, которые сближают языки программирования C# и Java, являются механизмы интерфейсов, обработки исключительных ситуаций, а также процессов или "нитей" (threads). "Сборка мусора" и пространства имен реализованы в этих двух языках сходным образом. Оба языка программирования характеризуются сильной (строгой) типизацией и динамической загрузкой кода при выполнении программы.

От своего прямого предшественника, языка программирования C++, языком C# унаследованы следующие механизмы: "перегруженные" операторы, небезопасные арифметические операции с плавающей точкой, а также ряд других особенностей синтаксиса. Но несмотря на то, что целый ряд конструктивных синтаксических механизмов и особенностей реализации унаследован языком программирования C# от прародителей (C++, Visual Basic и Java), возможности этого нового языка программирования не ограничиваются суммой возможностей его исторических предшественников.





Таблица 15.1. Основные возможности C#Подобен языкам Java, C++ и VB, однако является компонентно-ориентированным и более безопаснымДобавлен ряд новых черт (делегаты, индексаторы, механизм (un)boxing и др.)


Сходство с Java

объектно-ориентированный (единственное наследование)интерфейсыисключениянити (threads)пространство именсильная (строгая) типизациясборка мусораотражение (reflection)динамическая загрузка кода



Сходство с С++

"перегруженные операторы"арифметические операции с плавающей точкой относятся к небезопасному кодунекоторые особенности синтаксиса

 

 

 



К числу принципиально важных решений, которые реализованы корпорацией Microsoft в языке программирования C#, можно отнести следующие:

компонентно-ориентированный подход к программированию (который характерен и для идеологии Microsoft .NET в целом);

свойства как средство инкапсуляции данных (характерно также в целом для ООП);

обработка событий (имеются расширения, в том числе в части обработки исключений, в частности, оператор try);унифицированная система типизации (соответствует идеологии Microsoft .NET в целом);

делегаты (delegate – развитие указателя на функцию в языках C и C++);

индексаторы (indexer – операторы индекса для обращения к элементам класса-контейнера);

перегруженные операторы (развитие ООП);оператор foreach (обработка всех элементов классов-коллекций, аналог Visual Basic);механизмы boxing и unboxing для преобразования типов;

атрибуты (средство оперирования метаданными в COM-модели);

прямоугольные массивы (набор элементов с доступом по номеру индекса и одинаковым количеством столбцов и строк).


Рис. 15.1.  Структура программы на языке C#.

Особенности языка программирования C#, которые в большей степени отвечают целям настоящего учебного курса, будут рассмотрены в данной лекции более подробно.

Прежде всего, рассмотрим обобщенную структуру программы на языке программирования C#. Представим структуру программы на примере (см. рис.15.1).

Заметим, что программа на C# может состоять как из одного, так и из нескольких файлов, содержащих исходный текст на языке программирования C#.


Каждый такой файл имеет расширение .CS (в нашем примере файлы названы FileName1.cs, FileName2.cs и FileName3.cs).

Любой файл с исходным текстом на языке программирования C# может как содержать пространства имен, так и не содержать их (в нашем примере файл FileName2.cs включает три пространства имен, A, B и C, а FileName1.cs и FileName3.cs не содержат пространств имен).

Наконец, каждое пространство имен может как содержать описание классов (одного или нескольких), так и не содержать (в нашем примере пространство имен B содержит три описания трех классов (X, Y и Z), а пространства имен А и С не содержат ни одного описания классов).

Ранее в ходе лекций уже обсуждались механизмы boxing и unboxing, реализованные в различных средах разработки программного обеспечения Microsoft .NET. Напомним, что эти механизмы используются для преобразования типов выражений языков программирования из ссылочных в типы-значения и обратно.



Таблица 15.2. Сопоставление ссылочных типов и типов-значений. Типы-значенияСсылочные типыПеременная содержитПеременная хранитсяЗначение по умолчниюОператор присваиванияПример
значениессылку на значение
в стекев куче
0, false, ‘\0’ null
копирует значениекопирует ссылку
int i = 25;
int j = i;
i [25]
j [25]
string s = "John"
string sl = s;
s [ ]
[John]
sl [ ]


Рассмотрим более подробно реализацию двух основных семейств типов данных, а именно, ссылочных типов и типов-значений, применительно к языку программирования C#. Для определенности возьмем случай одного из простейших объектов языка программирования C#, а именно, переменной.

В соответствии с названиями, переменная в случае применения типов-значений содержит собственно значение, а при использовании ссылочных типов – не само значение, а лишь ссылку (указатель) на него.

Местом хранения переменной, определенной как тип-значение, является стек, а определенной как ссылочный тип – "куча" (последнее необходимо для динамического выделения и освобождения памяти для хранения переменной произвольным образом).

Значением, которым переменная инициализируется по умолчанию (необходимость выполнения этого требования диктуется идеологией безопасности Microsoft .NET) в случае определения посредством типа-значения является 0 (для целого или вещественного типа данных), false (для логического типа данных), ‘\0’(для строкового типа данных), а в случае определения посредством ссылочного типа – значение пустой ссылки null.

При выполнении оператора присваивания в случае переменной-значения копируется значение, а в случае переменной-ссылки – ссылка.

Приведенный пример иллюстрирует различия в реализации типов-ссылок и значений (см. табл. 15.2).





Таблица 15.3. Сопоставление отображений типов языков SML и C# в систему типов .NET.C# CTS (.NET) SML Диапазон
sbyte System.SByte --- -128..127
byte System.Byte byte 0..255
short System.Int16 int -32768..32767
ushort System.UInt16 word 0..65535
long System.Int64 --- -263..263-1
float System.Single real +1.5E-45..+3.4E+38 (32 Bit)
double System.Double --- +5E-324..+1.7E+308 (64 Bit)
decimal System.Decimal --- +1E-28..+7.9E+28 (128 Bit)
bool System.Boolean bool true, false
char System.Char char Символ (в коде unicode)


Даже из предварительного анализа таблицы видно, что система типизации языка программирования С# значительно богаче по сравнению с языком программирования SML. Можно заметить, что всякому типу языка программирования SML соответствует некоторый тип языка программирования С#, и их названия зачастую совпадают или бывают схожими.

Наконец, отметим, что все без исключения типы обоих языков программирования однозначно отображаются в систему типизации Microsoft .NET, верхним иерархическим элементом которой является пространство имен System.

Кроме понятия переменной, которое уже рассматривалось ранее в связи с типами-значениями и ссылочными типами, интересно провести исследование объектов-констант в языке программирования C#.

Напомним, что константные объекты в комбинаторной логике можно моделировать посредством комбинатора-канцелятора К с характеристикой Kxy = x.

Рассмотрим ряд примеров описания констант посредством оператора перечисления (заметим, что применяемый в C# способ описания констант изначально ориентирован на написание безопасного кода, поскольку содержит обязательную инициализацию).

Оператор перечисления enum языка C# представляет собой список поименованных констант. Описание производится непосредственно в пространстве имен с областью видимости, которую необходимо обеспечить для константы:

enum Color {red, blue, green} enum Access {personal=1, group=2, all=4} enum Access1 : byte {personal=1, group=2, all=4}

Пример использования константы после ее описания имеет следующий вид:



Color c = Color.blue;

Заметим, что для перечислимых констант должно быть указано полное имя:

Access a = Access.personal | Access.group; if((Access.personal & a) != 0) Console.WriteLine("access granted");

Подобно языку программирования SML в языке C# реализована возможность определения типа для того или иного языкового объекта. Для реализации такой возможности используется оператор typeof, который для любого заданного типа возвращает дескриптор этого типа.

Заметим, что дескриптор типа для объекта o возвращается операцией o.GetType(). Рассмотрим пример, иллюстрирующий использование оператора typeof:

Type t = typeof(int); Console.WriteLine(t.Name);

При выполнении данного примера тип рассматриваемого выражения будет определен системой как Int32, что вполне соответствует данным из сравнительной таблицы отображения типов языковых объектов C# и SML в систему типизации CTS Microsoft .NET.

Еще одной практически важной конструкцией C#, оперирующей типами, является оператор sizeof, который возвращает размер элемента данного типа (в байтах). Данный оператор применим только к типам-значениям и используется лишь в блоках небезопасного кода (т.к. размер структур может изменяться в зависимости от среды реализации); необходима компиляция кода командой

csc /unsafe xxx.cs.

Приведем пример использования оператора sizeof (заметим, что блоки небезопасного C#-кода выделяются посредством ключевого слова unsafe):

unsafe { Console.WriteLine( sizeof(int)); Console.WriteLine( sizeof(MyEnumType)); Console.WriteLine( sizeof(MyStructType)); }

Одним из важнейших видов основных объектов языка программирования C# являются структуры (аналоги структур имеются и в языке SML; в языках функционального программирования с примитивной системой типизации аналогами структур могут служить списки).

Рассмотрим пример описания структуры Point, моделирующей точку на плоскости:

struct Point { public int x, y; public Point (int x, int y) { this.x = x; this.y = y; } public void MoveTo (int a,int b) { x=a; y=b; } }



Заметим, что координаты точки x, y (в простейшем случае целочисленные), которые используются для определения ее положения, являются атрибутами объекта или полями структуры.

Операция Point является функцией инициализации объекта и называется конструктором. Обратите внимание, что унаследованный от C++ указатель this представляет собой ссылку на текущий объект.

Наконец, операция (или, иначе, метод) MoveTo изменяет текущее местоположение точки на пару целочисленных координат (a,b).

Для использования объекта-структуры необходимо проинициализировать объект-значение в стеке посредством конструктора. Затем можно вызвать метод MoveTo:

Point p = new Point(3, 4); p.MoveTo(10, 20);

Мы познакомились с одним из основных видов объектов языка программирования C#, а именно, со структурами.

В предыдущем примере фрагмента программы на C# дважды фигурировал идентификатор Point. В связи с этим уместно вспомнить о проблеме коллизий переменных и возможных путях ее преодоления (в предыдущей части курса для решения этой задачи использовался формализм, известный под названием чисел де Брейна). В языке программирования SML переменные могут быть описаны либо глобально (это описание распространяется на весь текст программы), либо локально в теле функции (это описание распространяется на весь текст функции).

В языке программирования C#, где объекты имеют существенно более сложную структуру, вводится понятие области описания, под которой понимают фрагмент программы, к которому относится данное описание.

Объекты языка программирования C# могут быть описаны:

в пространстве имен (классы, интерфейсы, структуры, перечисления, делегаты);в классе, интерфейсе, структуре (поля, методы, свойства, события, индексаторы);в перечислении (перечисляемые константы); в блоке (локальные переменные).

При этом принимаются следующие контекстные соглашения.

Во-первых, недопустимо двукратное описание в пределах данной области описания. Во-вторых, последовательность описаний является произвольной. Исключение составляют локальные переменные, которые необходимо описать до первого использования.



Кроме того, принимаются следующие соглашения об областях видимости.

Во-первых, любой идентификатор виден лишь из своей области описания. Во-вторых, область видимости можно изменить посредством модификаторов (private, protected).

В процессе изучения структуры программы на языке C# неоднократно употреблялся термин "пространство имен". В силу существования более значимых понятий объектно-ориентированного подхода к программированию в целом и языка программирования C# в частности, приводилось лишь общее описание данного термина. Рассмотрим пространства имен языка программирования C# более подробно.

Рассмотрим два файла X.cs и Y.cs, содержащих исходные тексты программ на языке C#.

Содержание файла X.cs:

namespace A { class C ... interface I... ... struct S... ... enum e ... ... delegate d ... namespace B { // полное имя: A.B ... } }

Содержание файла Y.cs:

namespace A { ... namespace B { ... } namespace C { ... } }

Заметим, что в файле X.cs содержатся описания пространств имен A и B, а в файле Y.cs – A, B и C, причем в обоих случаях последующие пространства имен вложены в A. При обращении к вложенному пространству имен нужно указывать его полное имя, например A.B.

Необходимо отметить, что пространства имен из разных файлов, имеющие один и тот же идентификатор, составляют единую область описания. Заметим также, что вложенные пространства имен составляют собственную (отдельную) область описания.



Аналогом оператора блока в языках функционального программирования является функция.

В языке программирования С# выделяют различные виды блоков, которые можно проиллюстрировать следующим примером:

void foo (int x) { // блок методов ... локальные переменные ... { // вложенный блок ... локальные переменные ... } for (int i = 0; ...) { // блок структурированных // операторов ... локальные переменные ... } }

Как видно из примера, блоки объединяют (структурированные) операторы и методы. Допускается использование блоков, вложенных один в другой. Для удобочитаемости текста программы глубину вложенности блока лучше визуализировать посредством табуляции.

Рассмотрим подробнее особенности описания локальных переменных в языке программирования C# на следующем примере программы:

void foo(int a) { int b; if (...) { int b; // ошибка: переменная b уже // описана в другом блоке int c; // пока описание корректно, // однако ... int d; ... } else { int a; // ошибка: переменная а уже // описана во внешнем блоке int d; // конфликтов с переменной d // из предыдущего блока нет } for (int i=0;...){ ... } for (int i=0;...){ ... } // фрагмент корректен: нет // конфликтов с переменной // i из предыдущего // цикла int c; // ошибка: c уже описана в // данном пространстве имен }

Рассмотрим функцию foo с одним целочисленным аргументом a, которая не возвращает значения.

Как видно из данного примера, коллизии описаний переменных возникают в случаях множественного описания внутри одного и того же блока или пространства имен.

Заметим попутно, что условный оператор if...else языка C# весьма схож с подобным оператором языка SML, а оператор for является оператором цикла.

Рассмотрим более сложный пример использования пространств имен в языке программирования C#.

Пусть программный проект на языке программирования C# содержит три файла с описаниями структур данных, оформленными в виде отдельных пространств имен:

Color.cs namespace Util { public enum Color { ... } }

Figures.cs namespace Util.Figures { public class Rect { ... } public class Circle { ... } }



Triangle.cs namespace Util.Figures { public class Triangle { ... } }

В данном случае при использовании полного имени пространства имен (Util.Figures) возможно обойтись без конкретизации классов (Rect), описанных внутри этого пространства имен. Однако в случае обращения к классам вне данного пространства имен необходимо использовать полное квалификационное имя объекта (Util.Color):

using Util.Figures; class Test { Rect r; // без указания полного // имени т.к. используем // (Util.Figures) Triangle t; Util.Color c; // c указанием // полного имени }

Проанализировав основные особенности языка программирования C#, а также исследовав структуру и принципы построения программ на этом языке, обозначим наиболее очевидные преимущества изучаемого языка программирования.

Прежде всего, необходимо отметить, что язык программирования C# претендует на подлинную объектную ориентированность (а всякая языковая сущность претендует на то, чтобы быть объектом).

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

Принципиально важным отличием от предшественников является изначальная ориентация на безопасность кода (что особенно заметно в сравнении с языками C и C++).

Унифицированная, максимально близкая по масштабу и гибкости к Common Type System, принятой в Microsoft .NET, система типизации является важным преимуществом языка C#.

Расширенная поддержка событийно-ориентированного программирования выгодно отличает язык программирования C# от целого ряда предшественников.

Язык программирования C# является "родным" для создания приложений в среде Microsoft .NET, поскольку наиболее тесно и эффективно интегрирован с ней.

Объединение лучших идей современных языков программирования (Java, C++, Visual Basic и др.) делает язык C# не просто суммой их достоинств, а языком программирования нового поколения.



Несмотря на значительное количество принципиальных преимуществ по сравнению с существующими аналогами, язык программирования C# не лишен и отдельных недостатков, которые, весьма вероятно, носят субъективный, локальный, временный характер.

Прежде всего, необходимо отметить то обстоятельство, что язык программирования C# имеет довольно сложный синтаксис (можно утверждать, что примерно 75% его синтаксических возможностей аналогичны языку программирования Java, 10% подобны языку программирования C++, а 5% – заимствованы из языка программирования Visual Basic). Объем действительно свежих концептуальных идей в языке C# относительно невысок (по мнению некоторых исследователей, он, составляет около 10% от общего объема конструкций языка).

Утверждение, что язык программирования C# является чисто объектным, допускает неоднозначную интерпретацию. Так, например, профессор K. Гуткнехт (K.Gutknecht) из института ETH (г. Цюрих, Швейцария) предложил альтернативную (так называемую "активную") объектную модель для разработанного им языка программирования Zonnon.

На сегодня компилятор и среда разработки программного обеспечения, поддерживающие язык C#, обладают относительно невысокой производительностью (т.е. код программы на языке C# компилируется и выполняется примерно в 100 раз медленнее, чем тот же код на языке C). Справедливости ради нужно отметить, что производительность программ на C# вполне сравнима с тем же показателем для языка Java.

Пока программы, написанные на языке C#, не могут функционировать под управлением альтернативных операционных систем (ведутся работы по обеспечению совместимости с операционными системами Linux и FreeBSD семейства UNIX).

Для более подробного самостоятельного ознакомления с тематикой лекции рекомендуется следующий список источников: [ 23, 38, 53, 63, 77].


Базовые конструкции языка C#


Объявление и инициализация переменных:

Тип_переменной имя_переменной [=значение];

Примеры:

int x; //обьявление переменной x x=100; //инициализация переменной x long w,z=100; //обьявление переменных w и z и //инициализация z long q=100*z; //обьявление переменной с //динамической инициализацией

Язык C# является языком программирования со строгим контролем типов данных. В языке C# выделяют две основные категории встроенных типов данных - простые типы и ссылочные типы. Характеристики основных простых типов данных в языке программирования C# представлены в таблице 6 .

Под областью видимости переменной в C# понимается блок кода, заключенный в фигурные скобки {}. Создание переменных осуществляется при входе управления программой в область видимости; уничтожение - при выходе из нее.

Таблица 6. Характеристики основных простых типовданных.

ТипОписаниеОбъем,бит
boolЗначение истина/ложь1
byte8-битовое беззнаковое целое8
charСимвол16
decimalЧисловой тип для финансовых вычислений128
doubleЧисло двойной точности с плавающей точкой64
floatЧисло с плавающей точкой32
intЗнаковое целое32
longДлинное знаковое целое64
sbyte8-битовое знаковое целое8
shortКороткое целое16
uintБеззнаковое целое32
ulongБеззнаковое длинное целое64
ushortБеззнаковое короткое целое16



Краткая информация о платформе .NET


Платформа .NET Framework предоставляет среду для поддержки создания и выполнения интероперабельных гетерогенных приложений. Основными особенностями данной платформы являются не зависящая от языка среда исполнения (Common Language Runtime, CLR) и библиотека классов .NET.



Основные управляющие операторы


Условный оператор:

if (условие) оператор [else оператор];

if (условие1) оператор1; else if (условие2) оператор2; else if (условие3) оператор3; ...

Оператор выбора:

switch (выражение){ case константа1: оператор1; ... break;

case константа2: операторX1; ... break; ... default: операторZ1; ... break; }

Оператор цикла:

for(инициализация, условие_выхода, итерация) оператор;

while (условие_продолжения) оператор;

do оператор;

while (условие продолжения);



Порядок выполнения работы


Сформулировать формальную постановку задачи (например, в виде алгоритма на псевдокоде).Реализовать программу на языке C# в соответствии с вариантом исполнения.Сравнить разработанную программу на C# с ранее созданной программой на SML.



Пример элементарной программы на C#


Программа 'Hello, World' на языке C# выглядит следующим образом:

using System;

class HelloWorld01 { public static void Main() { Console.Write("Hello, World!"); Console.ReadLine(); } }



Пространства имен


Понятие пространства имен определяет область объявления данных, что позволяет хранить каждый набор имен данных отдельно от других наборов. В языке С# имена, объявленные в одном пространстве имен, не конфликтуют с именами, объявленными в другом пространстве имен. Библиотекой .NET Framework (т.е. библиотекой языка С#) используется пространство имен System.

Для того, чтобы сделать видимыми пространства имен без указания полного имени (т.е. с использованием оператора '.') в языке C# существует директива using следующего вида.

Синтаксис:

using имя_пространства_имен;

Кроме того, в языке C# возможно использование псевдонимов для имен следующего вида:

using псевдоним = имя;

Для объявления пространства имен в языке C# используется ключевое слово namespace.

Синтаксис:

namespace имя {члены_пространства_имен}



Варианты заданий


Написать C# программу, реализующую функцию согласно варианту задания. Исходные данные вводятся с клавиатуры.

Реализовать функцию вычисления суммы двух целых чисел.Реализовать функцию вычисления разности двух целых чисел.Реализовать функцию вычисления произведения двух целых чисел.Реализовать функцию вычисления частного двух целых чисел.Реализовать функцию вычисления суммы двух вещественных чисел.Реализовать функцию вычисления разности двух вещественных чисел.Реализовать функцию вычисления произведения двух вещественных чисел.Реализовать функцию вычисления частного двух вещественных чисел.Реализовать функцию возведения целого числа в квадрат.Реализовать функцию возведения в квадрат суммы двух целых чисел.Реализовать функцию возведения в квадрат разности двух целых чисел.Реализовать функцию возведения в квадрат произведения двух целых чисел.Реализовать функцию возведения в квадрат частного двух целых чисел.Реализовать функцию возведения в квадрат суммы двух вещественных чисел.Реализовать функцию возведения в квадрат разности двух вещественных чисел.Реализовать функцию возведения в квадрат произведения двух вещественных чисел.Реализовать функцию возведения в квадрат частного двух вещественных чисел.Реализовать функцию возведения в куб целого числа.Реализовать функцию возведения в куб суммы двух целых чисел.Реализовать функцию возведения в куб разности двух целых чисел.Реализовать функцию возведения в куб произведения двух целых чисел.Реализовать функцию возведения в куб частного двух целых чисел.Реализовать функцию возведения в куб суммы двух вещественных чисел.Реализовать функцию возведения в куб разности двух вещественных чисел.Реализовать функцию возведения в куб произведения двух вещественных чисел.Реализовать функцию возведения в куб частного двух вещественных чисел.



Семантика основных конструкций языка программирования C#


  Лекции

Введение в теорию программирования. Объектно-ориентированный подход

0.   Вступительная лекция

1.   Объектно-ориентированный подход ...

2.   Платформа.NET и ее применение д...

3.   Основные понятия языка программ...

4.   Разработка элементарных программ...

5.   Семантика основных конструкций языка программирования C#

6.   Основные понятия объектно-ориент...

7.   Объекты и классы

8.   Теория типов и типизация в .NET

9.   Концепция наследования и ее реал...

10.   Концепция инкапсуляции и ее реа...

11.   Концепция полиморфизма и ее реа...

12.   Полиморфные методы

13.   Расширенные возможности полимор...

14.   Расширенные возможности языка пр...

15.   Событийно управляемое программи...

16.   Событийно-ориентированное програ...

17.   Компонентное программирование в ...

18.   Проектирование и реализация гете...

    Экзамен
    Сдать экзамен экстерном
    Литература
    Предметный указатель


Введение в теорию программирования. Объектно-ориентированный подход версия для локальной работы

5. Лекция: Семантика основных конструкций языка программирования C#
Страницы:

1

|

2

|

вопросы | »

|

учебники

|

для печати и PDA

  Если Вы заметили ошибку - сообщите нам.  
В данной лекции будут рассмотрены вопросы, относящиеся к понятийному аппарату и выразительным возможностям семантического представления формальных теорий и языков программирования. При этом основное внимание будет уделено сопоставлению семантики языков объектно-ориентированного и функционального программирования. В качестве примеров языков программирования будут выступать уже знакомый нам по предыдущему курсу язык SML и изучаемый нами язык C#.
Включить комментарии

|| Настройки

|| Модерация

|| Помощь


Далее, сформулируем вычислительную модель на основе состояний программы языка C#, для наглядности систематизировав ее в виде следующей таблицы:



Таблица 16.1. Вычислительная модель на основе состояний программы языка C#ПараметрДоменСоотношение
СостояниеState (s) State = Memory
ПамятьMemory (m) Memory = Ide
[Value + (unbound)]
ЗначениеValue (v) Value = Int + Bool


Заметим, что состояние программы в произвольный момент времени определяется состоянием "памяти" абстрактной машины той или иной формы. При этом под памятью понимается отображение из домена идентификаторов в домен значений (т.е. аналог связывания переменной со значением в ламбда-исчислении). Для корректной обработки исключительных ситуаций, возникающих в случае свободных переменных, вводится дополнительный элемент unbound. Домен значений представляет собой дизъюнктную сумму доменов, содержащих существующие в языке C# типы Int и Bool.

В соответствии с намеченной схемой рассуждений, перейдем к описанию семантических предложений, которые описывают значение денотатов (т.е. правильно построенных конструкций) языка C#.

Приведем семантические предложения для выражений языка программирования C#:

E : Exp -> [State -> [[Value ( State] + {error}]];

E[E]s = (v,s'),

если

v - значение E в s,

s'- состояние после означивания;

E[E]s = error,

если возникает ошибка несоответствия типов.

Из приведенных соотношений следует, что вычисление значения выражения языка программирования C# приводит к такому изменению состояния, что происходит связывание переменной со значением, либо (в случае невозможности связывания по причине несоответствия типов переменной и значения) вырабатывается ошибка. При этом состояние программы изменяется с s на s'.

Приведем семантическое предложение для команд языка программирования C#:

С:Com->[State->[State+{error}]].

Из приведенного соотношения следует, что вычисление значения команды языка программирования C# приводит, вообще говоря, к изменению состояния, причем возможно возникновение ситуации (например, несоответствия типов в ходе присваивания), при которой вырабатывается ошибка.



В соответствии с намеченной схемой рассуждений, перейдем к описанию семантических предложений, которые описывают значение конкретных денотатов (т.е. правильно построенных конструкций) языка C#. Рассмотрим семантические предложения для денотатов констант целочисленного типа языка C#:

E[0]s=(0,s); E[1]s=(1,s);

Как видно из приведенных соотношений, денотатами констант целочисленного типа являются значения этих констант (в форме упорядоченных пар вида "значение"-"состояние"), причем смены состояния программы не происходит. Рассмотрим семантические предложения для денотатов констант логического типа языка C#:

E[true]s=(true,s); E[false]s=(false,s);

Как видно из приведенных соотношений, денотатами констант логического типа являются значения этих констант (в форме упорядоченных пар вида "значение"-"состояние"), причем смены состояния программы не происходит. Рассмотрим семантическое предложение для денотатов идентификаторов языка C#:

E[I]s=(m,I=unbound) error, -> (m,I,s).

Как видно из приведенного соотношения, при возможности связывания денотатами идентификаторов являются идентификаторы, связанные со значениями (в форме упорядоченных троек вида "значение в памяти"-"идентификатор"-"состояние"), причем смены состояния программы не происходит, а при невозможности - выдается сообщение об ошибке.

Рассмотрим семантические предложения для денотатов выражений языка C#:

E[!E]s = (E [E] s=(v, s')) (isBool -> (not v, s'), error), error; E [E1=E2] s = (E [E1]s = (v1, s1)) -> (E [E2] s1 = (v2, s2)) -> (v1 = v2, s2), error), error; E [E1+E2] s = (E [E1] s=(v1, s1)) -> (E [E2] s1 = (v2, s2)) -> (IsNum v1 and IsNum v2 -> v1 + v2, s2), error),error),error.

Проанализируем полученные соотношения.

Денотатом отрицания выражения является отрицание его значения; причем состояние программы изменяется. В случае несоответствия типов или небулевости выражения генерируется сообщение об ошибке.

Денотатом присваивания является присвоенное значение в новом состоянии.


В случае несоответствия типов генерируется сообщение об ошибке.

Денотатом сложения является значение суммы в новом состоянии. В случае несоответствия типов генерируется сообщение об ошибке.

В качестве упражнения предлагается самостоятельно разработать семантические предложения для денотатов команд языка программирования C#.

В ходе лекции была представлена классификация подходов к семантике языков программирования, признан целесообразным денотационный подход, который проиллюстрирован примером языка C# - ограниченного подмножества C#.

Важнейший вывод, к которому мы неизбежно приходим к концу лекции, заключается в том, что семантики рассмотренных нами языков программирования SMalL и C# (которые являются подмножествами реальных языков SML и C#) имеют весьма много общего.

Более того, сходство семантики рассмотренных нами конструкций (команд и выражений) языков программирования SMalL и C# весьма прозрачно и очевидно. Для иллюстрации справедливости этого утверждения достаточно сопоставить результаты исследования семантики рассматриваемых языков программирования.

По результатам такого сопоставления можно прийти к закономерному выводу о том, что существует явное, непосредственное отображение конструкций рассмотренных языковых подмножеств друг в друга, причем это отображение носит взаимно однозначный характер.

Попутно мы можем сделать еще один важный вывод. Формализация денотационной семантики на основе доменов с помощью теории вычислений Д. Скотта является адекватной моделью семантики выражений и команд рассмотренных языков программирования (а также функционального и объектно-ориентированного подходов в целом). Таким образом, семантика языков функционального и объектно-ориентированного программирования достаточно близка к семантике формальных теорий, на которых они основаны (в частности, это справедливо для ламбда-исчисления и языков SML и C#). Кроме того, теория вычислений является актуальной и адекватной формализацией семантики, а денотационный подход - целесообразным для моделирования семантики различных языков программирования.

В завершение лекции хотелось бы указать на аналогию теории вычислений Д. Скотта и ламбда-исчисления (как метатеорий) со средой Microsoft .NET (как метасредой), которые принципиально обеспечивают возможность "погружения" в себя различных языков и подходов к программированию.

Для более подробного самостоятельного ознакомления с тематикой лекции рекомендуется следующий список источников: [ 24, 35, 39, 42, 45, 53, 69, 73].


Изложим понятийный аппарат


Изложим понятийный аппарат объектно-ориентированного подхода к программированию.

Прежде чем будут изложены математически строгие определения (в том числе в терминах теории дескрипций), попытаемся сформировать предварительное представление об основных сущностях данного подхода.

Сформулируем на интуитивном уровне определения таких основополагающих для объектно-ориентированного подхода к программированию понятий, как объект, класс, свойство и метод.

Под объектом будем понимать математическое представление сущности реального мира (или предметной области), которое используется для моделирования.

Классом будем называть весьма общую сущность, которая может быть определена как совокупность элементов (нужно заметить, что класс при объектно-ориентированном подходе к программированию - это, как правило, первичное, неопределяемое понятие, до некоторой степени аналогичное теоретико-математическому понятию множества, или, точнее, домена).

Под свойством (или атрибутом) будем понимать пропозициональную функцию, определенную на произвольном типе (данных).

Методом (или функцией) назовем операцию, которая определена над объектами того или иного класса.

Заметим, что класс при объектно-ориентированном подходе является аналогом понятия типа в том смысле, что к нему относят лишь объекты, отобранные по определенному правилу. Это правило можно формализовать математически посредством предикатной функции, т.е. функции, область значений которой совпадает со значениями истинности: "истина" и "ложь". При этом тот или иной объект относится к классу, если значение аппликации функции к данному объекту истинно, и не относится в противном случае.

Функцию такого рода принято называть индивидуализирующей функцией. Индивидуализирующая функция фактически является моделью экспертной классификации.

Важным условием при исследовании объектно-ориентированного подхода к программированию является установление взаимосвязей фундаментальных сущностей. Отметим в этой связи, что понятие класса является изначально более общим, чем понятие объекта.
Точнее, говорят, что объект является экземпляром (instantiation) класса.

Таким образом, класс может рассматриваться как совокупность объектов (подобно тому как множество или домен есть совокупность элементов).

В рамках объектно-ориентированного подхода к программированию произвольный класс может быть элементарным либо подразделяться на подклассы (подобно тому как множество или домен подразделяется на подмножества или субдомены).

Например, более общий класс PERSON может содержать внутри себя подкласс STUDENT, который, в свою очередь, содержит конкретный объект John_Smith.

Как отмечалось во вступительной лекции, в 90-е годы В.Э. Вольфенгагеном (Vyatcheslav E. Wolfengagen) была создана так называемая двухуровневая схема концептуализации, основанная на двукратном применении постулата свертывания, до известной степени аналогичного операции ламбда-абстракции.

Рассмотрим более подробно основные аспекты данной формализации объектно-ориентированного подхода к программированию. В общих чертах схема построения модели выглядит следующим образом.

Основу модели составляет типизированный вариант ламбда-исчисления, семантика которого моделируется посредством полных и непрерывных решеток Д. Скотта.

Для описания объектов произвольной сложности вводятся аппликативные структуры с приписыванием типов. Последнее обстоятельство необходимо для формализации иерархии классов, в которые объединяются объекты.

Для формализации определений используются определенные дескрипции Скотта-Фурмана вида:

IxФ,

что означает "тот единственный объект x, для которого значение индивидуализирующей функции Ф истинно".

При таком подходе произвольный объект моделируется упорядоченной тройкой элементов вида

<концепт, индивид, состояние>,

составляющие которой соединены соотнесениями.

Основным соотношением формальной схемы двухуровневой концептуализации является так называемый принцип концептуализации, подобный принципам свертывания, которые (в той или иной форме) присутствуют практически во всех математических теориях, исследованных ранее в рамках курса (в частности, в исследованных ранее в рамках курса ламбда-исчислении и комбинаторной логике).

Формальная запись принципа концептуализации посредством определенных дескрипций выглядит следующим образом:

||Ix(x)Ф(x)||i = d <=> _ {d} = {d

D| ||Ф(d)||i=true}

Приведем словесную интерпретацию формулы:

Значением индивидного концепта является функция из соотнесений в индивиды.

С точки зрения рассматриваемой формальной теории, произвольный объект d предметной области D может быть единственным образом индивидуализирован посредством функции Ф (где i означает соотнесение).

Заметим, что при соотнесении общей теории с языками программирования домен D можно также интерпретировать как класс, а элемент домена d - как объект языка программирования.


Основные понятия объектно-ориентированного подхода: объекты, классы и методы


  Лекции

Введение в теорию программирования. Объектно-ориентированный подход

0.   Вступительная лекция

1.   Объектно-ориентированный подход ...

2.   Платформа.NET и ее применение д...

3.   Основные понятия языка программ...

4.   Разработка элементарных программ...

5.   Семантика основных конструкций я...

6.   Основные понятия объектно-ориентированного подхода: объекты, классы и методы

7.   Объекты и классы

8.   Теория типов и типизация в .NET

9.   Концепция наследования и ее реал...

10.   Концепция инкапсуляции и ее реа...

11.   Концепция полиморфизма и ее реа...

12.   Полиморфные методы

13.   Расширенные возможности полимор...

14.   Расширенные возможности языка пр...

15.   Событийно управляемое программи...

16.   Событийно-ориентированное програ...

17.   Компонентное программирование в ...

18.   Проектирование и реализация гете...

    Экзамен
    Сдать экзамен экстерном
    Литература
    Предметный указатель

Введение в теорию программирования. Объектно-ориентированный подход версия для локальной работы

6. Лекция: Основные понятия объектно-ориентированного подхода: объекты, классы и методы
Страницы:

1

|

2

|

3

|

вопросы | »

|

учебники

|

для печати и PDA

  Если Вы заметили ошибку - сообщите нам.  
В данной лекции будут рассмотрены вопросы, относящиеся к идеологии, методологии и практике моделирования основных элементов объектно-ориентированного подхода к программированию посредством двухуровневой концептуализации. Особенности практической реализации основных аспектов концепции ООП описаны на примере языка программирования C#.

Включить комментарии

|| Настройки

|| Модерация

|| Помощь


Как и поля, методы описываются в блоке описания класса.

Рассмотрим особенности использования методов на примере следующей программы на языке C#, представляющей описание класса C с полями sum и n и методами Add и Mean:

class C { int sum = 0, n = 0; public void Add (int x){ sum = sum + x; n++; //процедура } public float Mean(){ return(float)sum/n; //функция(должна возвратить //значение) } }

Прежде всего, отметим, что методы в языке программирования C# делятся на функции (которые обязаны возвращать значение) и процедуры (которые могут и не возвращать значения, на что указывает тип void). В данном примере Add - процедура, а Mean - функция, в которой возврат значения явно указывается оператором return. Доступ к методу, как и к полю, можно получить изнутри класса:

this.Add(3); float x = Mean();

а также из других классов, с явным указанием полного имени:

C c = new C(); c.Add(3); float x = c.Mean();

Заметим, что оператор this представляет собой указатель на текущий объект.

Продолжая аналогию между полями и методами как элементами классов, мы приходим к понятию статического метода. Рассмотрим особенности реализации статических методов в языке программирования C# на следующем примере:

class Rectangle { static Color defaultColor; public static void ResetColor() { defaultColor = Color.white; } }

Как явствует из приведенного примера, под статическим методом понимается операция, определенная над статическими элементами классов (т.е. над статическими полями).

В данном примере описания класса Rectangle статическим является метод ResetColor (заметим, что он не возвращает значения, т.е. ResetColor - это статическая процедура).

По аналогии с предыдущими случаями, для доступа к статическому методу изнутри класса достаточно указать только краткое имя данного метода:

ResetColor();

В случае доступа из сторонних классов необходимо указать полное имя статического метода:

Rectangle.ResetColor();

Исследовав особенности описания и управления поведением основных элементов классов, объектов и методов для динамического и статического случаев, кратко остановимся на особенностях наследования свойств (полей и методов) классов объектов языка программирования C#.


Для иллюстрации приведем следующий пример фрагмента программы на языке C#:

class Stack { int[] values; int top = 0; public Stack(int size){ ... } public void Push(int x){ ... } public int Pop(){ ... } }

В данной программе приведено (с сокращениями) описание класса, моделирующего стек (аналогичный стеку КАМ) посредством массива элементов values с вершиной top, функциями создания стека Stack размером size и "выталкивания" Push элемента x из стека, а также "вталкивания" Pop элемента в стек.

Аналогично объектам ссылочных типов, объекты классов (как принципиально динамические) хранятся в динамической области памяти (или так называемой "куче"). В силу ограничений безопасности программного кода любой объект языка программирования C# до использования необходимо инициализировать оператором new, например:

Stack s = new Stack(100);

Заметим, что наследование классами свойств других классов может быть как единичным, так и множественным. Последнее реализуется посредством множественных интерфейсов (что приводит к множественному наследованию типов).

Подводя итоги обсуждения основных понятий объектно-ориентированного подхода к программированию (классов, объектов и методов) применительно к языку программирования C#, кратко отметим достоинства и недостатки подхода.

К преимуществам объектно-ориентированного подхода следует отнести:

интуитивную близость произвольной предметной области;возможность моделирования сколь угодно сложной предметной области, высокий уровень абстракции (рассмотренные примеры дают представление о "масштабируемости" моделирования сложных объектов);событийно-ориентированный подход (динамика объектов и возможность манипулирования ими посредством методов приводят к управлению объектами посредством событий); возможность повторного использования описаний (основана на обращении к полям и методам извне описания классов, а также на использовании механизма наследования);параметризация методов обработки объектов (основана на использовании механизма интерфейсов, которые будут подробно рассмотрены в ходе дальнейших лекций).

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

Для более подробного самостоятельного ознакомления с тематикой лекции рекомендуется следующий список источников: [ 24, 28, 36, 66-69, 79-81].


Рассмотрев интуитивное определение


Рассмотрев интуитивное определение понятия класса, а также представив домены как формальную модель классов языков программирования в целом, остановимся более подробно на классах в языке объектно-ориентированного программирования C#.

По аналогии с другими известными языками объектно-ориентированного программирования (в частности, C++ и Java), под классом в языке C# понимается ни что иное, как ссылочный тип, определенный пользователем.

При этом для классов языка программирования C# допустимо только единичное наследование. В случае необходимости реализации множественного наследования возможно наследование посредством механизма интерфейсов, который будет подробнее рассмотрен далее в ходе курса.

Членами (или, иначе, элементами) класса языка программирования C# могут являться следующие конструкции:

константа, поле, метод, оператор, конструктор, деструктор;

свойство, индексатор, событие;статические и инициализированные члены.

Доступ к членам класса определяется исходя из значения модификатора области действия идентификатора класса, который может принимать следующие значения: public, protected, private (данное значение используется по умолчанию), internal, protected internal.

Инициализация объекта класса языка программирования C# производится посредством оператора new, о котором будет рассказано ниже.

Рассмотрим манипулирование классами на примере следующих фрагментов программ на языке C#.

Прежде всего, приведем простейшее описание класса. Описание класса C c целочисленным полем value на языке C# имеет вид:

class C { ... int value = 0; ... }

Заметим, что в описании класса C на языке программирования C# кроме рассмотренного поля value могут присутствовать и другие поля (т.е. атрибуты объектов класса) допустимых в языке C# типов, а также методы (т.е. способы манипулирования объектами данного класса).

В языке программирования C# инициализация поля (т.е. связывание его с начальным значением) не является обязательной. Для обеспечения безопасности программного кода и в силу реализации принципа инкапсуляции, инициализация поля некоторого класса C не должна открывать возможностей для доступа к полям и методам данного типа.
При этом доступ к элементам класса внутри класса реализуется посредством обращения и не требует полного имени объекта:

... value ...

В отличие от предыдущего случая, доступ из сторонних классов требует указания полного имени объекта (в примере последовательно производятся инициализация и обращение):

C c = new C(); ... c.value ...

Рассмотрим более развернутый пример описания классов и манипулирования их элементами.

Приведем описание класса Rectangle, моделирующего прямоугольник с полями origin, width и height, моделирующими, соответственно, начальную точку (с парой координат), ширину и высоту, а также методом MoveTo, моделирующим перемещение начальной точки в заданную:

class Rectangle { Point origin; public int width, height; public Rectangle(){ origin = new Point(0,0); width=height=0; } public Rectangle ( Point p, int w, int h){ origin = p; width = w; height = h; } public void MoveTo (Point p) { origin = p; } }

Заметим, что модификатор области видимости для данного класса и его элементов разрешает общедоступное применение (public). Рассмотрим пример использования класса Rectangle:

Rectangle r = new Rectangle( new Point(10,20),5,5); int area = r.width * r.height; r.MoveTo(new Point(3,3));

Заметим, что в данном примере последовательно осуществляются инициализация объекта класса Rectangle с начальной точкой (10,20), шириной и высотой в пять единиц (т.е. квадрата), подсчет его площади area и перемещение начала отсчета в точку с координатами (3,3).

В результате анализа рассмотренных примеров становится очевидным, что объект является принципиально динамическим и изменяет состояние в зависимости от соотнесения (времени и внешних воздействий).

В этой связи исследуем более подробно простейший, статический случай полей объекта, который в языке программирования C# выделен в самостоятельный синтаксический элемент, характеризующийся независимостью от состояния объекта (и потому условно принадлежащий к классу). Приведем модифицированный пример предыдущего класса для случая статических полей:

class Rectangle { static Color defaultColor; // для каждого класса static readonly int scale; // для каждого класса int x, y, width,height; // для каждого объекта ... }

Заметим, что статические поля defaultColor и scale остаются неизменными внутри класса, тогда как динамические поля x, y, width и height индивидуально изменяются в зависимости от состояния каждого из объектов класса. Доступ изнутри класса осуществляется посредством обращения:

... defaultColor ... scale ...

а из внешних классов - посредством обращения:

... Rectangle.defaultColor ... Rectangle.scale ...

с указанием полных имен объектов. Поскольку статические поля являются неизменными со временем, они реализуются выделением памяти из статической области.


Классы и обьекты


Понятие класса является фундаментальным в ООП и служит основой для создания объектов. В описании класса определяются данные (т.е. переменные) и код (т.е. методы), манипулирующий этими данными. Объекты являются экземплярами класса.

Методы и переменные, составляющие класс, называются членами класса. При определении класса объявляются данные, которые он содержит, и код, манипулирующий этими данными. Данные содержатся в переменных экземпляра, которые определены классом, а код содержится в методах. В языке С# определены несколько специфических разновидностей членов класса. К ним относятся: переменные экземпляра, статические переменные, константы, методы, конструкторы, деструкторы, индексаторы, события, операторы и свойства.

Инициализация переменных в объекте (как в экземпляре класса) производится непосредственно в конструкторе класса. В составе класса может быть определено несколько конструкторов.

Синтаксис определения класса:

class имя_класса{ тип_доступа тип имя_переменной1; тип_доступа тип имя_переменной2; ... тип_доступа возвращаемый_тип имя_метода1(список_параметров) {тело_метода} }

где тип_доступа может принимать одно из следующих значений: public, private, protected, internal. Члены класса с типом доступа public являются общедоступными (т.е. доступны из любой точки программы за пределами данного класса), с типом доступа protected – внутри членов данного класса и его производных, с типом доступа private – только для других членов данного класса. Тип доступа internal применяется для типов, доступных в пределах одной сборки.

Приведем пример описания класса:

class Animal{ public string Name; private int Weight; protected int Type; public int Animal(int W, int T, string N){ Weight=W; Type=T; Name=N; } public int GetWeight(){return Weight;} }



Наследование


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

Синтаксис описания объекта:

class имя_класса : имя_родительского_класса { тело_класса }

Пример описания объекта:

class Predator:Animal{ private int Speed; }

Наследование формирует иерархию классов на основе отношения частичного порядка ISA ("являться").

Иерархия может быть построена и для объектов. В этом случае она имеет структуру, которая строится на основе отношения структурного вхождения ("часть-целое"), при котором один объект является частью другого.



Понятия конструктора и деструктора


Под конструктором класса будем понимать метод для инициализации объекта при его создании. Конструктор имеет то же имя, что и его класс. В конструкторах тип возвращаемого значения не указывается явно. Конструкторы используются для присваивания начальных значений переменным экземпляра, определенным классом, и для выполнения любых других процедур инициализации, необходимых для создания объекта.

Конструктор существует для любого класса, независимо от того, определен он в явном виде или нет. Умолчаниями языка С# предусмотрено наличие конструктора, который присваивает нулевые значения всем переменным экземпляра (для переменных типов-значений) и значения null (для переменных ссылочного типа). В случае явного определения конструктора класса конструктор по умолчанию не используется.

Синтаксис описания конструктора:

имя_класса(список_параметров) {тело_конструктора}

Под деструктором будем понимать метод, который автоматически вызывается при уничтожении объекта класса (непосредственно перед началом процедуры "сборки мусора"). Деструкторы не имеют параметров и не возвращают значений.

Синтаксис описания деструктора:

~имя_класса() {тело_деструктора}



Порядок выполнения работы


Разработать методы и свойства для каждого из определяемых классов.Реализовать программу на C# в соответствии с вариантом исполнения.



Создание обьекта


Синтаксис:

имя_класса имя_обьекта = new имя_класса();

При создании обьекта (т.е. экземпляра класса) происходит вызов соответствующего конструктора класса.



Варианты заданий


Построить иерархию классов в соответствии с вариантом задания:

Студент, преподаватель, персона, заведующий кафедройСлужащий, персона, рабочий, инженерРабочий, кадры, инженер, администрацияДеталь, механизм, изделие, узелОрганизация, страховая компания, нефтегазовая компания, заводЖурнал, книга, печатное издание, учебникТест, экзамен, выпускной экзамен, испытаниеМесто, область, город, мегаполисИгрушка, продукт, товар, молочный продуктКвитанция, накладная, документ, счетАвтомобиль, поезд, транспортное средство, экспрессДвигатель, двигатель внутреннего сгорания, дизель, реактивный двигательРеспублика, монархия, королевство, государствоМлекопитающее, парнокопытное, птица, животноеКорабль, пароход, парусник, корвет



Теория типов и типизация в .NET


Обсудим основы дисциплины типов в рамках .NET. Напомним, что история развития математических формализаций для типовых теорий изложена во вступительной лекции, а основа теории типов - в лекции 6.

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

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

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

Перейдем к языкам программирования и практике проектирования и реализации программных систем. В случае построения языка программирования по принципу строгой типизации несоответствия типов фиксируются до начала этапа выполнения программы (на этапе контроля соответствия типов в ходе трансляции), что гарантирует отсутствие семантических (смысловых) и логических ошибок и безопасность программного кода.

Напомним классификацию систем типизации в языках программирования.

Исторически наиболее распространенной для языков программирования является строгая типизация. При таком формировании системы типов в языке в любой момент существования любого языкового объекта существует однозначное соответствие между объектом и его типом. Другими словами, можно запрограммировать функцию, определяющую тип объекта, подобную ранее рассмотренной нами функции typeof языка программирования C#. Строго типизированными являются классические императивные языки программирования Pascal, FORTRAN, PL/I и др. Отметим, что классический вариант языка программирования C не является строго типизированным.

Сильная типизация необходима для обеспечения корректности связывания переменных со значениями до выполнения программы.
табл. 18.1):

При этом типы- значения распадаются на следующие подтипы:



элементарные (в частности, целочисленные и вещественные):

int i; float x;



перечислимые (в частности, моделирующие переключатели):

enum State {Off, On}



структурные (в частности, моделирующие точки на плоскости):

struct Point {int x,y;}



В свою очередь, ссылочные типы подразделяются на следующие подтипы:

корневой подтип (указатели на произвольные объекты в иерархии типов в Common Type System):

object



строковые (указатели на строки символов):

string



классы (указатели на объекты типа class):

class Foo: Bar, IFoo {...}



интерфейсы (указатели на объекты типа interface):

interface IFoo: IBar {...}



массивы (в частности, указатели на строки из 10 символов):

string[] a = new string[10];



делегаты (усовершенствованные указатели на функцию):

delegate void Empty();




Рис. 18.2.  Иерархия типов языка C# (фрагмент).

Многообразие типов можно разделить на предопределенные (заранее заданные системой программирования) и определенные пользователем (user defined), см. рис. 18.2.

К последним относятся перечисления, массивы, классы, интерфейсы и делегаты (указатели на функцию).

Предопределенные типы делятся на ссылочные типы (объекты и символьные строки) и типы-значения (встроенные - короткие и длинные целые со знаком и без знака, а также числа с плавающей точкой - с одинарной и двойной точностью).

Как уже отмечалось, над элементами типов могут осуществляться преобразования из одного типа данных в другой (например, из строки в число или наоборот). При этом такие преобразования могут инициироваться как программистом (т.е. происходить явно), так и системой программирования (т.е. происходить неявным образом).

Неявные преобразования инициируются Common Type System и производятся автоматически. При этом результат неявного преобразования всегда успешен и не приводит к потере точности.

Явные преобразования инициируются программистом или пользователем приложения, а следовательно, требуют явного вызова и при этом могут завершаться ошибкой, а также приводить к потере точности.

Приведем ряд примеров преобразований типов на языке программирования C#:

int x = 25; long y = x; // неявное short z = (short) x; // явное double d = 3.141592536; float f = (float) d; // явное long l = (long) d; // явное

Заметим, что система Common Type System среды Microsoft .NET обеспечивает безопасную типизацию, т.е. гарантирует отсутствие побочных эффектов (переполнение оперативной памяти компьютера, некорректное преобразование типов и т.д.). Заметим также, что как явные, так и неявные преобразования типов могут инициироваться пользователем.



Значение механизма пространств имен состоит в том, что появляется возможность логической структуризации системы типизации Common Type System в среде разработки приложений Microsoft .NET.

Описания пространств имен по аналогии с описаниями типов данных размещаются в файлах.

Перечислим основные свойства, которыми характеризуются пространства имен в среде Microsoft .NET:



пространства имен могут объединять различные сборки;

пространства имен могут быть вложенными друг в друга;между пространствами имен и файлами не существует однозначного соответствия (т.е. отображения, переводящего название пространства имен в имя файла);полное имя типа должно содержать все необходимые пространства имен.

Для иллюстрации применения механизма пространств имен в среде программирования Microsoft .NET приведем развернутый пример описания пространств имен на языке программирования C#:

namespace N1{ // N1 class C1{ // N1.C1 class C2{ // N1.C1.C2 } } namespace N2{ //N1.N2 class C2{ // N1.N2.C2 } } }

Рассмотренный пример содержит описания пространств двух имен: пространства N1 с описанием классов C1 и C2 и пространства N2 с описанием класса C2.

Заметим, что в комментариях к каждой строке программы на языке C# приведены полные наименования пространств имен. Так, для обращения к классу C2, описанному в пространстве имен N1, нужно использовать полное имя N1.C1.C2, а для обращения к классу C2, описанному в пространстве имен N2 - полное имя N1.N2.C2.

Таким образом, при адекватном употреблении полных квалификационных наименований пространств имен удается избежать коллизии обозначений типов.

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

Оказывается, что непременное использование полных имен типов в среде программирования Microsoft .NET является избыточным требованием.

Для экономии трудозатрат и во избежание коллизий обозначений при разработке крупных программных систем в языке программирования C# предусмотрен оператор using, к рассмотрению которого мы и переходим.


Концепция наследования и ее реализация в языке C#


Формализуем понятие наследования и обсудим его значение для объектно-ориентированного подхода к программированию. Напомним, что эволюция теоретических основ наследования была рассмотрена во вступительной лекции.

Под наследованием в дальнейшем будем понимать свойство производного объекта сохранять поведение родительского объекта. Под поведением будем иметь в виду для математического объекта его атрибуты и операции над ним, а для языкового объекта ООП - поля и методы.

Таким образом, применительно к языку программирования концепция наследования означает, что свойства и методы базового класса равно применимы к производным от него классам. Заметим, что дочерний объект не обязательно наследует все без исключения атрибуты и операции родительского, а лишь некоторые из них. Такой подход характерен как для классов объектов в целом, так и для отдельных их конкретизаций, или, иначе, экземпляров.

Теоретическая концепция наследования удовлетворительно моделируется посредством отношения (или, точнее, иерархии) частичного порядка. Существует целый ряд формализаций наследования, но наиболее адекватными и концептуально ясными являются графические модели. Среди них следует выделить уже упомянутые ранее подходы: фреймовую нотацию Руссопулоса и диаграммы Хассе.

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

Во-первых, оно является рефлексивным, т.е. любой объект языка программирования или формальной модели предметной области находится в отношении частичного порядка с самим собой. Формальная запись свойства рефлексивности для отношения частичного порядка ISA выглядит следующим образом:

a: a ISA a.

Другой важной особенностью отношения частичного порядка является транзитивность. Суть транзитивности состоит в том, что если существует цепочка из (трех) объектов, связанных отношением частичного порядка, то первый и последний элементы этой цепочки можно связать тем же отношением. Это свойство гарантирует построение на множестве графа отношения частичного порядка в виде "решетки".
Формальная запись свойства транзитивности для отношения частичного порядка выглядит следующим образом:

a,b,c: a ISA b, b ISA c
a ISA c.

Наконец, еще одним фундаментальным свойством отношения частичного порядка как модели наследования является антисимметричность. Это свойство разрешает наследование только в одном направлении (и запрещает его в противоположном). Формальная запись свойства антисимметричности для отношения частичного порядка выглядит следующим образом:

a,b : a ISA b
NOT (b ISA a).

По завершении исследования свойств отношения частичного порядка, перейдем к рассмотрению формализаций, моделирующих наследование. Проиллюстрируем графическую интерпретацию отношения частичного порядка на примере фреймовой нотации Руссопулоса. Рассмотрим следующий фрейм (рис. 19.1), который связывает отношением частичного порядка понятия "сущность" (THING), "юридическое лицо" (LEGAL.PERSON), "учреждение" (INSTITUTION), "работодатель" (EMPLOYER), "кадровое агентство" (RECRUITER), "физический объект" (PHYSICAL.OBJECT), "одушевленный объект" (ANIMATE.OBJECT), "человек" (PERSON), "мужчина" (MALE.PERSON) и "женщина" (FEMALE.PERSON).


Рис. 19.1.  Пример фрейма.

Как видно из структуры фрейма, понятия (или, точнее, концепты) изображены в овалах. Направленные ISA-дуги соединяют понятия, образуя иерархию с направлением в сторону увеличения уровня общности (абстракции), например, от понятия "мужчина" к понятию "сущность". Рефлексивные и транзитивные дуги опущены для удобства восприятия; их без труда можно восстановить. Ориентированность дуг характеризует антисимметричность отношения частичного порядка: любая из дуг может иметь только одну стрелку со стороны увеличения уровня абстракции.

Другой продуктивной формализацией, моделирующей, в частности, наследование, является диаграмма Хассе. Проиллюстрируем использование диаграмм Хассе для графической интерпретации отношения между элементами класса или домена (см.рис. 19.2).


Рис. 19.2.  Пример диаграммы Хассе.

Диаграмма Хассе состоит из точек, которые представляют элементы множества (точнее, домена или класса), а также из соединяющих их линий, которые представляют собой отношения между элементами класса или домена (в данном случае интерпретируется отношение частичного порядка).

Данный пример иллюстрирует отношение IS IN ("является подмножеством") между множествами {}, {1}, {2}, {3}, {1,2}, {1,3}, {2,3} и {1,2,3}.

Заметим, что в случае графической интерпретации отношения частичного порядка с помощью диаграммы Хассе свойство антисимметричности рассматриваемого отношения было бы отображено в явном виде.


Концепция инкапсуляции и ее реализация в языке C#


Формализуем понятие инкапсуляции в рамках объектно-ориентированного подхода к программированию.

В неформальной постановке вопроса под инкапсуляцией будем понимать доступность объекта исключительно посредством его свойств и методов.

Другими словами, концепция инкапсуляции призвана обеспечивать безопасность проектирования и реализации программного обеспечения на основе локализации манипулирования объектом в областях его полей и методов.

Иначе говоря, свойствами объекта можно оперировать исключительно посредством его методов. Это замечание касается как свойств, явно определенных в описании объекта, так и свойств, унаследованных данным объектом от другого (других).

Практическая важность концепции инкапсуляции для современных языков объектно-ориентированного программирования (в том числе и для языка C#) определяется следующими фундаментальными свойствами.

Прежде всего, реализация концепции инкапсуляции обеспечивает совместное хранение данных (или, иначе, полей) и функций (или, иначе, методов) внутри объекта.

Как следствие, механизм инкапсуляции приводит к сокрытию информации о внутреннем "устройстве" объекта данных (или, в терминах языков ООП, свойств и методов объекта) от пользователя того или иного объектно-ориентированного приложения.

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

Рассмотрев определение понятия инкапсуляции (пока на интуитивном уровне), перейдем к описанию формализаций этой фундаментальной для объектно-ориентированного программирования концепции на основе уже известных нам формальных теорий computer science.

Оказывается, что понятие инкапсуляции вполне адекватно формализуется посредством таких теоретических формальных систем, как ламбда-исчисление А.Черча и комбинаторная логика Х.Карри.

При этом при интерпретации концепции инкапсуляции в терминах формальной системы ламбда-исчисления, в роли объектов выступают ламбда-термы, в роли свойств - связанные переменные, а в роли методов - свободные переменные.


В случае же интерпретации концепции инкапсуляции в терминах формальной системы комбинаторной логики в роли объектов выступают комбинаторы, в роли свойств - переменные, а в роли методов - комбинаторы.

Поясним более подробно реализацию механизма сокрытия информации посредством концепции инкапсуляции.

Рассмотрим в достаточно обобщенном виде схему взаимодействия объекта и данных.

Вначале представим схему такого рода для традиционного императивного языка программирования (например C и Pascal). При этом в нашем рассуждении под термином "объект" будем понимать произвольный объект языка программирования, безотносительно к концепции объектно-ориентированного программирования.

В этом случае объявления данных и процедуры обработки данных отделены друг от друга. Зачастую в ранних языках программирования описания языковых объектов и процедуры манипулирования ими выделены в особые разделы.

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

В отличие от предыдущей схемы, при объектно-ориентированном подходе к проектированию и реализации программного обеспечения взаимодействие объектов языка программирования и конкретизирующих его данных реализуется принципиально иным образом. По существу, объект и данные составляют единое и неделимое целое.

Прежде всего, определение (или описание свойств классов) и процедуры манипулирования этими свойствами (или методы) для каждого объекта языка программирования при объектно-ориентированном подходе хранятся совместно.



Кроме того, среда проектирования и реализации программного обеспечения (например, Microsoft Visual Studio .NET) не предоставляет иных возможностей доступа к объекту, кроме как посредством методов, изначально предназначенных для манипулирования данным объектом.

Таким образом, концепция инкапсуляции в языках объектно-ориентированного программирования служит целям обеспечения единообразия определения, хранения и работы с информацией о языковых объектах.

Заметим, что инкапсуляция является безусловно необходимым требованием для каждого объекта.

В то же время в практике программирования степень инкапсуляции объекта (в широком значении этого слова) определяется его описанием, а также описанием порождающих его объектов (в действительности это, как правило, описания базовых и производных классов).

В этой связи в языках объектно-ориентированного программирования вводится понятие области видимости как степени доступности произвольного языкового объекта.

Применительно к языку программирования C# области видимости объектов подразделяются на следующие виды.

Общедоступные объекты описываются с помощью зарезервированного слова public и характеризуются доступностью из произвольного места программы, для которого определено пространство имен с описанием рассматриваемого объекта. При этом элементы интерфейсов и перечислений являются общедоступными (public) объектами по умолчанию.

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

Наконец, элементы классов и структур, в частности, поля, методы, свойства и вложенные типы являются по умолчанию доступными из описаний соответствующих классов или структур и описываются с помощью зарезервированного слова private.

Проиллюстрируем обсуждение использования модификаторов областей видимости объектов (public и private) следующим содержательным примером фрагмента программы на языке C#:



public class Stack{ private int[] val; // private используется // и по умолчанию private int top; // private используется // и по умолчанию public Stack(){ ... } public void Push(int x){ ... } public int Pop(){ ... } }

Как видно из приведенного примера, фрагмент программы на языке C# содержит описание класса стека Stack, реализованного на основе массива целочисленных элементов (поле val). Голова стека представляет собой целочисленное значение (поле top). Над стеком определены операции инициализации (метод Stack), а также вставки (метод Push) и удаления (метод Pop) элемента.

Как явствует из примера, класс Stack и все манипулирующие его объектами методы (Stack, Push и Pop) являются общедоступными (public), тогда как поля являются доступными локально (private), т.е. только из описания данного класса.

Заметим, что в приведенном выше примере фрагмента программы с описанием областей видимости использовались только базовые возможности модификаторов видимости языка программирования C#.

Оказывается, что язык программирования C# располагает механизмами реализации дополнительных (или расширенных) по сравнению с базовыми областей видимости объектов.

Рассмотрим более подробно особенности основных типов расширенных областей видимости объектов в языке программирования C#.

К числу расширенных областей видимости следует отнести доступность языковых объектов как непосредственно из класса с описанием объекта, так и из его производных классов. В данном случае для описания языкового объекта используется зарезервированное слово protected.

Кроме того, для описания доступности языкового объекта лишь из сборки с его описанием используется зарезервированное слово internal.

Наконец, для описания доступности языкового объекта непосредственно из класса с описанием данного объекта, его производных классов, а также из сборки с описанием данного объекта используется зарезервированное слово protected internal.

Проиллюстрируем обсуждение использования модификаторов расширенных областей видимости объектов (protected и internal) следующим содержательным примером фрагмента программы на языке C#:



class Stack { protected int[] values = new int[32]; protected int top = -1; public void Push(int x) { ... } public int Pop() { ... } } class BetterStack : Stack { public bool Contains(int x) { foreach (int y in values) if(x==y) return true; return false; } } class Client { Stack s = new Stack(); ... s.values[0]; ... // ошибка при компиляции! }

Как видно из приведенного примера, фрагмент программы на языке C# содержит описание класса стека Stack (для хранения 32 целочисленных элементов и значением -1 в вершине), а также реализованного на его основе усовершенствованного класса стека BetterStack, дополнительно реализующего повторяющиеся элементы стека. В отличие от предыдущего примера, все поля класса стека Stack доступны как из данного класса, так и из классов, производных от него, поскольку описаны посредством ключевого слова protected.

После обсуждения использования (степеней) инкапсуляции применительно к классам в целом, рассмотрим особенности приложения данной концепции к таким фундаментальным объектам языка программирования C# как поля и константы.

Инициализация не является безусловно необходимой для полей в языке программирования C#. Тем не менее, доступ к полям и методам изначально запрещен (что соответствует использованию по умолчанию модификатора доступа private). В случае структуры поля инициализации не подлежат.

Простейшей иллюстрацией описания поля в языке программирования C# является следующий пример, содержащий определение класса C с целочисленным полем value:

class C { int value = 0; }

В случае константы инициализация также не является необходимым требованием. Тем не менее, значение константы должно быть вычислимым в процессе компиляции.

Простейшей иллюстрацией описания константы в языке программирования C# является следующий пример, содержащий определение константы size, представляющей собой целочисленное значение двойной длины (long):

const long size = ((long)int.MaxValue+1)/4;

Заметим, что фрагмент

...(long) ...

в правой части присваивания представляет собой явное преобразование типов языковых объектов.


Абстрактные классы


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

Для объявления абстрактного метода используется модификатор abstract. Поскольку абстрактный метод неявно является виртуальным, модификатор virtual при объявлении такого метода не используется.

Абстрактные классы предназначены исключительно для создания иерархии классов, поэтому запрещено создание объектов абстрактного класса.



Концепция полиморфизма


Понятие полиморфизма является одной из фундаментальных составляющих объектно-ориентированного программирования и позволяет определять в наследуемом классе методы, которые являются общими для всех наследующих классов. При этом наследующий класс может определять специфическую реализацию для некоторых (а возможно и всех) полиморфных методов. Важнейший принцип полиморфизма можно сформулировать фразой: "один интерфейс, несколько методов". Благодаря полиморфизму можно пользоваться методами, не обладая точными знаниями о типе объектов.

Основными механизмами для реализации концепции полиморфизма в ООП (в том числе, в языке C#) являются виртуальные методы и абстрактные классы.



Описание абстрактного класса


Синтаксис:

abstract class имя {тело_класса};

Пример:

abstract class Animal { public string Name; protected int Weight; private int Type; abstract void Feed(); public int Animal(int W, int T, string N) { Weight=W; Type=T; Name=N; } public int GetWeight(){ return Weight; } } class Predator:Animal { private int Speed; override void Feed(int Food){ Weight += Food; } }



Описание абстрактного метода


Синтаксис:

virtual тип имя (список_параметров) {тело_метода};



Порядок выполнения работы


Изменить иерархию классов и реализовать ее на С#.Показать на примере одного из методов, присутствующих в каждом классе, свойство полиморфизма.



Варианты заданий


Расширить иерархию классов из лабораторной работы №7 с использованием виртуального класса в качестве основы иерархии. Показать пример использования полиморфизма методов.



Виртуальные методы


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

Выбор конкретизации виртуального метода, которая будет использована, осуществляется в соответствии с типом объекта, на который указывает ссылочная переменная во время выполнения программы. Другими словами, именно тип объекта, на который указывает ссылка (а не тип ссылочной переменной), определяет вызываемую версию виртуального метода. Таким образом, если класс содержит виртуальный метод и от этого класса были унаследованы другие классы, в которых определены свои версии метода, при ссылке переменной типа наследуемого класса на различные типы объектов вызываются различные версии виртуального метода.

При определении виртуального метода в составе наследуемого класса перед типом возвращаемого значения указывается ключевое слово virtual, а при переопределении виртуального метода в наследующем классе используется модификатор override. В определении виртуального метода недопустимо использование модификаторов static и abstract.

Виртуальный метод не обязательно должен быть переопределенным (overridden). Если наследующий класс не предоставляет собственную версию виртуального метода, то используется метод наследуемого класса.

Переопределение метода положено в основу концепции динамического выбора вызываемого метода. Выбор вызываемого переопределенного метода осуществляется во время выполнения программы, а не во время ее компиляции.



Расширенные возможности полиморфизма в языке C#


Предыдущая лекция была посвящена базовым аспектам полиморфизма. Обсудим более тонкие механизмы, предусмотренные в языке C# для реализации сложных полиморфных объектов.

Проиллюстрируем особенности использования абстрактных свойств и методов следующим фрагментом программы на языке C#:

abstract class Sequence { public abstract void Add(object x); // метод public abstract string Name{ get; } // свойство public abstract object this [int i] { get; set; } // индексатор }

class List : Sequence { public override void Add(object x) { ... }

public override string Name { get { ... } } public override object this [int i] { get { ... } set{ ... } } }

Как видно из приведенного примера, фрагмент программы на языке C# представляет собой описание абстрактных классов Sequence и List, реализующих потоковое чтение и запись данных (get и set).

Заметим, что описание абстрактного класса Sequence реализовано явно посредством зарезервированного слова abstract.

Необходимо обратить внимание на то обстоятельство, что поскольку в производных классах требуется замещение методов, методы Add и Name класса List оснащены описателем override.

Еще одним средством реализации расширенного полиморфизма в языке программирования C# является механизм, известный под названием "запечатанных" (sealed) классов.

Под "запечатанными" классами мы будем понимать нерасширяемые классы, которые могут наследовать свойства других классов. Решение об использовании механизма "запечатывания" при описании приоритетных или, иначе, замещенных (override) методов принимается в индивидуальном порядке.

Рассмотрим соображения, обосновывающие использование механизма "запечатанных" классов в языке программирования C#. Прежде всего, благодаря реализации данного механизма предотвращается изменение семантики класса. Таким образом, существенно повышается безопасность разрабатываемого программного обеспечения. А за счет того, что вызов "запечатанных" методов предполагает использование статического связывания, значительно возрастает эффективность реализуемых приложений.


Проиллюстрируем особенности использования механизма "запечатанных" классов следующим фрагментом программы на языке C#:

sealed class Account : Asset { long val; public void Deposit (long x) { ... } public void Withdraw (long x) { ... } ... }

Как видно из приведенного примера, фрагмент программы на языке C# представляет собой описание "запечатанного" класса Account, реализующего абстрактные методы Deposit и Withdraw для занесения средств на счет и их снятия со счета.

В ходе курса уже обсуждалась стратегия динамического связывания переменных со значениями при функциональном и объектно-ориентированном подходах.

Проиллюстрируем особенности реализации динамического связывания в языке программирования C# следующим фрагментом программы:

class A { public virtual void WhoAreYou() { Console.WriteLine("I am an A"); } }

class B : A { public override void WhoAreYou() { Console.WriteLine("I am a B"); } }

Как видно из приведенного примера, данный фрагмент программы содержит описания класса A с виртуальным методом WhoAreYou, который выводит на стандартное устройство вывода сообщение о принадлежности к классу A, а также замещенного класса B с виртуальным методом WhoAreYou, который выводит на стандартное устройство вывода сообщение о принадлежности к классу B. При таком подходе сообщение запускает метод, который динамически определяет принадлежность к классу (в приведенном ниже примере создается и идентифицируется объект класса B):

A a = new B(); a.WhoAreYou(); // "I am a B"

Заметим, что существенной особенностью рассматриваемого примера является то обстоятельство, что B - это замещенный метод.

В этой связи, каждый из методов, который способен обрабатывать A, может также обрабатывать B. Проиллюстрируем этот факт следующим фрагментом программы на языке C#:

void Use (A x) { x.WhoAreYou(); } Use(new A()); // "I am an A" Use(new B()); // "I am a B"

Как видно из приведенного фрагмента программы, применение метода Use, реализующего идентификацию объекта класса A, приводит к результату для объекта "I am an A" класса A и к результату "I am a B" для объекта класса B.Корректность получаемого результата для класса B (несмотря на детерминированное описание класса A как параметра метода Use) объясняется тем обстоятельством, что класс B является замещенным.


Делегаты


Под делегатом будем понимать объект, который имеет ссылку на метод. Делегат позволяет осуществлять выбор вызываемого метода во время выполнения программы. Фактическим значением делегата является адрес области оперативной памяти, по которому распложена точка входа метода.

Важным свойством делегата является то, что он позволяет указать в коде программы вызов метода, однако фактически вызываемый метод определяется во время выполнения программы, а не во время ее компиляции.

Делегат объявляется с помощью ключевого слова delegate, за которым указывается тип возвращаемого значения, имя делегата и список параметров вызываемых методов.



Интерфейсы


В языке программирования C# с целью изоляции описательной части класса от его реализации используется механизм интерфейсов.

Понятие интерфейса является расширением идеи абстрактных классов и методов. Синтаксис интерфейсов подобен синтаксису абстрактных классов. Объявление интерфейсов осуществляется с помощью ключевого слова interface. При этом методы интерфейса не поддерживают реализации.

Членами интерфейса могут быть методы, свойства, индексаторы и события.

Интерфейс может реализовываться произвольным количеством классов. Один класс, в свою очередь, может реализовывать любое число интерфейсов.

Каждый класс, включающий интерфейс, должен реализовывать его методы. В интерфейсе для методов неявным образом присутствует модификатор доступа типа public. При этом явное указание модификатора доступа недопустимо.



Многоадресность делегатов


Многоадресность - это способность делегата хранить несколько ссылок на различные методы, что позволяет при вызове делегата инициировать эту цепочку методов.

Для формирования цепочки методов следует создать экземпляр делегата, и, пользуясь операторами "+" или "+=", добавлять методы к цепочке. Для удаления метода из цепочки используется оператор "-" или "-=". Делегаты, хранящие несколько ссылок, должны иметь тип возвращаемого значения void.



Описание делегата


Синтаксис:

delegate тип_возвращаемого_значения имя_делегата (список_параметров);

Характерной особенностью делегата является возможность его использования для вызова любого метода, который соответствует подписи делегата. Это дает возможность определить, какой из методов должен быть вызван, во время выполнения программы. Вызываемый метод может быть методом экземпляра, ассоциированным с объектом, либо статическим методом, ассоциированным с классом. Метод можно вызывать только в том случае, если его подпись соответствует подписи делегата.



Описание интерфейса


Синтаксис:

[атрибуты] [модификаторы] interface Имя_интерфейса[ :список_родительских_интерфейсов] { обьявление_свойств_и_методов}

Пример:

interface Species { string Species(); void Feed(); }

class Cheetah:Animal,Species{ private string ScientificName; public string Species() { return ScientificName; } public void Feed() { Weight++; } }

В языке C# допустимо объявление ссылочных переменных интерфейсного типа. Такие переменные могут ссылаться на любой объект, реализующий их интерфейсы. При вызове метода объекта по интерфейсной ссылке происходит вызов той конкретизации метода, которая реализуется данным объектом.

В языке C# допустимо наследование интерфейсов. При этом синтаксис аналогичен таковому для наследования классов. В случае, если класс реализует интерфейс, который наследует другой интерфейс, необходимо предусмотреть реализацию всех членов, определенных в составе иерархии наследования интерфейсов.



Порядок выполнения работы


Реализовать программу на C# в соответствии с вариантом исполнения.Для проверки всех методов данного класса следует использовать многоадресный делегат.



Варианты заданий


Реализовать для иерархии из лабораторной работы №8 механизм интерфейсов, при этом один из классов должен реализовывать как минимум два интерфейса. Использовать для проверки всех методов данного класса многоадресный делегат.



Генерация исключений


Исключения автоматически генерируются средой программирования. Однако исключение может быть и явно сгенерировано посредством оператора throw.



Исключительные ситуации


Исключительная ситуация (или, короче, исключение) представляет собой ту или иную ошибку или отклонение от обычного сценария во время выполнения программы. С помощью подсистемы обработки исключений языка С# можно обрабатывать такие ошибки, не вызывая аварийного завершения программы.

Для обработки исключений в языке С# примененяются следующие ключевые слова: try, catch, throw, finally. Перечисленные ключевые слова образуют взаимосвязанную подсистему, в которой использование одного из ключевых слов влечет за собой использование других.

Обработка исключений в языке C# реализуется на основе операторных блоков try и catch.



Конструкция try/catch с блоком finally


Синтаксис:

try { // Блок кода, выполняющий мониторинг ошибок } catch (ExcepTypel ех1) { //1 Обработка исключения ExcepTypel. }

catch (ЕхсерТуре2 ех2) { // 2 Обработка исключения ЕхсерТуре2 . finally { // Код блока finally. }

Блок finally будет вызываться независимо от того, появится исключение или нет, а также независимо от причин возникновения такового.



Наследование классов исключений


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

Создаваемые пользователем классы исключений автоматически получают доступные для них свойства и методы, определенные в классе Exception.



Обработка событий


Под событием будем понимать автоматическое извещение о каком-либо действии среды программирования или пользователя. События являются членами класса и объявляются с использованием ключевого слова event. Реализация механизма событий в языке программирования C# основана на использовании делегатов.



Оператор throw


Синтаксис:

throw exceptOb;

Исключение, перехваченное одним оператором catch, может генерироваться повторно, и благодаря этому перехватываться внешним оператором catch. Для этого указывается ключевое слово throw без имени исключения.



Описание блока try и catch


Синтаксис:

try { Блок_кода_для_которого_выполняется мониторинг_ошибок} catch (ExcepTypel ехОb) { Обработчик_исключений_ExcepTypel } catch (ЕхсерТуре2 ехОb) {Обработчик_исключений_ЕхсерТуре2 }

Основные системные исключения приведены в таблице 10.

Тип исключения в операторе catch должен соответствовать типу перехватываемого исключения. Неперехваченное исключение непременно приводит к досрочному прекращению выполнения программы.

Таблица 10. Основные системные исключения

ИсключениеЗначение
ArrayTypeMismatchExceptionТип сохраненного значения несовместим с типом массива
DivideByZeroExceptionПредпринята попытка деления на ноль
IndexOutOfRangeExceptionИндекс массива выходит за пределы диапазона
InvalidCastExceptionНекорректное преобразование в процессе выполнения
OutOfMemoryExceptionВызов new был неудачным из-за недостатка памяти
Overflow/ExceptionПереполнение при выполнении арифметической операции
StackOverflowExceptionПереполнение стека

Для выполнения перехвата исключений вне зависимости от их типа (перехват всех исключений) возможно использование оператора catch без параметров.



Порядок выполнения работы


Реализовать программу на языке C# в соответствии с вариантом исполнения.Графически проиллюстрировать место реализованного исключения в общей иерархии.



Широковещательные события


Некоторые события могут активизировать несколько процедур обработки, в том числе определенных в других объектах. Такие события называются широковещательными. Реализация механизма широковещательных событий в языке программирования C# основана на использовании многоадресных делегатов.

Пример управления событиями посредством делегата:

// Объявление делегата, на основе которого // будет определено событие. delegate void MyEventHandler () ;

// Объявление класса, в котором // инициируется событие. class MyEvent { public event MyEventHandler activate; // В этом методе инициируется событие.

public void fire() { if (activate != null) activate(); } }

class X { public void Xhandler() { Console.WriteLine("Событие получено объектом класса X."); } }

class Y { public void Yhandler() { Console.WriteLine("Событие получено объектом класса Y."); } }

class EventDemo { static void handler() { Console.WriteLine("Событие получено объектом класса EventDemo.") }

public static void Main() { MyEvent evt = new MyEvent(); X xOb = new X(); Y yOb = new Y();

// Добавление методов handler (), XhandlerО // и YhandlerO в цепочку обработчиков события. evt.activate += new MyEventHandler(handler); evt.activate += new MyEventHandler(xOb.Xhandler); evt.activate += new MyEventHandler(yOb.Yhandler); evt.fire(); Console.WriteLine(); evt.activate -= new MyEventHandler(xOb.Xhandler); evt.fire(); } }



Варианты заданий


Реализовать обработку ошибок для лабораторной работы №9, при этом переопределив с помощью наследования событие:

StackOverflowExceptionArrayTypeMismatchExceptionDivideByZeroExceptionIndexOutOfRangeExceptionInvalidCastExceptionOutOfMemoryExceptionOverflowException



Возврат из исключения


Так как оператор catch не вызывается из программы, то после выполнения блока catch управление не передается обратно оператору программы, при выполнении которого возникло исключение. Выполнение программы продолжается с операторов, находящихся после блока catch.

С целью предотвращения этой ситуации возможно указание блока кода, который вызывается после выхода из блока try/catch, с помощью блока finally в конце последовательности try/catch.



Компонентное программирование в .NET


Пожалуй, наиболее существенным нововведением идеологии Microsoft .NET является компонентно-ориентированный подход к программированию.

Во-первых, следует отметить то обстоятельство, что компонентно-ориентированный подход к проектированию и реализации программных систем и комплексов является в некотором смысле развитием объектно-ориентированного и практически более пригоден для разработки крупных и распределенных систем (например, корпоративных приложений).

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

Говоря о свойствах компонентов, следует прежде всего отметить, что это существенно более крупные единицы, чем объекты (в том смысле, что объект представляет собой конструкцию уровня языка программирования). Другими отличиями компонентов от традиционных объектов являются возможность содержать множественные классы и (в большинстве случаев) независимость от языка программирования.

Заметим, что, автор и пользователь компонента, вообще говоря, территориально распределены и используют разные языки. Вполне возможно, что они не только пишут программы, но и говорят на разных языках.

Получив представление о компонентах и их отличиях от традиционных объектов ООП, рассмотрим существующие подходы к моделированию вариаций компонентного подхода в современной практике проектирования и реализации программных комплексов и систем.

Прежде всего, поскольку основной вычислительной средой для исследования в данном курсе является Microsoft .NET, обсудим особенности модели для компонентно-ориентированной разработки программного обеспечения, предложенной корпорацией Microsoft.
Эта модель называется компонентной объектной моделью (или Component Object Model, COM) и является изначальным стандартом, принятым для компонентной разработки приложений в корпорации Microsoft.

Компонентная модель COM определяет протокол для конкретизации (т.е. создания экземпляров) и использования компонент (по аналогии с классами и объектами) как внутри одного и того же процесса, так и между различными процессами или компьютерами, предназначенными для выполнения того или иного программного проекта, основанного на компонентной технологии. Модель COM является достаточно универсальной и используется в качестве фундамента для таких технологий проектирования и реализации программного обеспечения, как ActiveX, OLE и целого ряда других технологий. Приложения для COM-модели могут создаваться средствами таких языков и сред разработки как Visual Basic, C++, .NET и т.д.

Другим известным стандартом для компонентной модели является стандарт компании Sun Microsystems, известный как JavaBeans, который не обладает свойством независимости от языка программирования. Еще один широко используемый стандарт компонентного программирования - архитектура объектных запросов CORBA (несмотря на поддержку многоязычной разработки приложений, существуют сложности, связанные с отображением одного языка реализации в другой; для этой цели применяется достаточно громоздкий интерфейс, основанный на специальном языке описания IDL).

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



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

Заметим, что промежуточный язык IL всегда компилируется в естественный (native) код до выполнения программы.

Для более эффективного манипулирования системой типизации компонент создаваемого программного обеспечения в рамках модели COM, концепция .NET предусматривает механизм пространств имен (namespace).

Пространством имен будем называть механизм среды Microsoft .NET, предназначенный для идентификации типов объектов языков программирования и среды реализации.

Описания пространств имен по аналогии с описаниями типов данных размещаются в файлах. Перечислим основные свойства, которыми характеризуются пространства имен в среде Microsoft .NET. Прежде всего, пространства имен могут как объединять различные сборки, так и быть вложенными друг в друга. Кроме того, файлы с описаниями могут содержать множественные пространства имен. Важно отметить, что между пространствами имен и файлами не существует однозначного соответствия. Наконец, полное имя типа должно содержать все необходимые пространства имен.

Приведем пример файла xxx.cs с описанием множественных пространств имен на языке C#:

xxx.cs namespace A { ... } namespace B { ... } namespace C { ... }

Приведем пример файла xxx.cs с описанием пространств имен на языке C# с неоднозначным (с учетом предыдущего примера) соответствием файлов и пространств имен:

xxx.cs namespace A { class C { ... } }

Кроме свойств, перечисленных выше, механизм пространств имен в среде вычислений .NET обладает еще целым рядом важных особенностей.

Так, допускается импорт пространств имен с использованием зарезервированного слова using языка программирования C# (похожий подход реализован в модулях языка Modula-2).Проиллюстрируем эту особенность фрагментом программы на языке C#:

using System;

Кроме того, существует возможность импорта одних пространств имен в другие. Проиллюстрируем это свойство фрагментом программы на языке C#:

using A; namespace B { using C; ... }

Отметим также, что для явного указания полных и сокращенных имен разрешаются псевдонимы (alias). Проиллюстрируем это свойство следующим примером программы на языке C#:

using F = System.Windows.Forms; ... F.Button b;

Как видно из приведенного фрагмента программы, для удобства вызова стандартного пространства имен .NET под именем System.Windows.Forms применяется псевдоним F.


Директива reference


Синтаксис:

Reference файл_с_кодом_компонента_1 [...]



Гетерогенные приложения


Платформа программирования .NET изначально разрабатывалась для построения приложений на компонентной основе и обеспечения независимости взаимодействия компонентов от языка программирования. Благодаря этому в рамках одного приложения могут быть использованы компоненты, реализующие различные подходы к программированию.

В качестве основной структурной единицы компонентного программирования выступает решение (solution), которое может состоять из сборок (assembly). Сборка представляет из себя управляемую динамическую библиотеку (DLL-файл) для .NET или приложение (EXE-файл). В каждом решении должна быть хотя бы одна сборка.

Для взаимодействия между сборками используется механизм ссылок. Ссылка (reference) - это пространство имен из одной сборки, доступное в другой. После добавления дополнительного проекта к решению и ссылки к основному проекту, в коде основного проекта можно пользоваться пространствами имен дополнительного.



Описание директивы export


Синтаксис:

export имя1[, имя2 ...]

При создании нового проекта SML.NET платформа автоматически заносит имя основной структуры SML-проекта в создаваемый файл. В случае добавления дополнительных файлов или создания дополнительных экспортируемых структур экспортом необходимо управлять вручную.

Для создания ссылки на другие проекты .NET в решении для SML.NET необходимо добавить директиву reference.



Порядок выполнения работы


Реализовать программу на C# в соответствии с вариантом исполнения.Представить в графическом виде взаимодействие компонентов в среде .NET.



Варианты заданий


Оснастить графическим интерфейсом на языке C# следующие функции на языке SML.

Реализовать функцию на языке программирования SML, которая выполняет синтаксический разбор следующего аппликативного выражения по ассоциации влево с построением бинарного дерева:

a(bc)ac(bc)(ac)(bc)(ab)(c(de))a(b(cd)(ef))a(b(cd)(ef)g)a(b((cd)(ef))(a(bc(de)f)gh)abb(cdd(e)fg)(ab(c(de))f(g(hi))j)

Реализовать функцию на языке программирования SML, которая выполняет синтаксический разбор следующего аппликативного выражения по ассоциации вправо с построением бинарного дерева:

a(bc)ac(bc)(ac)(bc)(ab)(c(de))a(b(cd)(ef))a(b(cd)(ef)g)a(b((cd)(ef))(a(bc(de)f)gh)abb(cdd(e)fg)(ab(c(de))f(g(hi))j)



Взаимодействие с SML.NET на компонентном уровне


Код SML.NET может создавать пространство имен, видимое в рамках других приложений. Для этого в файл script.smlnet добавляется директива export. Данная директива экспортирует фрагмент программы на языке программирования (скажем, SML) в форме объекта или структуры в пространство имен .NET. Последнее, в свою очередь, может быть использовано в любой сборке решения при добавлении соответствующей ссылки.