Основные понятия языка программирования 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].
Содержание раздела