федеральное государственное образовательное

учреждение Высшего профессионального образования

«Южный федеральный университет»

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

Учебное пособие

Ростов-на-Дону

2008

Рецензенты:

доцент, канд. ф.-м. наук

доцент, канд. ф.-м. наук

Объектно-ориентированное программирование на языке C#: Учебное пособие. - Ростов-на-Дону, 2008.

Аннотация

В учебном пособии рассматриваются вопросы, касающиеся объектно-ориентированного программирования на языке C#. Оно состоит из 5 модулей. В модуле 1 освещаются общие вопросы устройства платформы.NET, в модуле 2 дается быстрое введение в язык C#, модуль 3 является основным и посвящен особенностям работы с классами на C#, в модуле 4 изучаются основные стандартные классы коллекций и связанные с ними интерфейсы.

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

Автор: .

Введение

В данном учебном пособии рассматриваются вопросы объектно-ориентированного программирования на языке C# – универсальном языке программирования, разработанном фирмой Microsoft в 2000 г. Данный язык содержит множество современных конструкций объектно-ориентированного программирования, позволяющих писать качественный защищенный код. Кроме того, язык опирается на платформу.NET, предоставляющую значительные преимущества при программировании: единая система типов, унификация обработки исключений, модульность и пр.

НЕ нашли? Не то? Что вы ищете?

Пособие разбито на 4 модуля. В первом модуле описываются особенности платформы.NET, второй модуль содержит введение в C#, третий модуль является основным и описывает основные объектно-ориентированные возможности C#. Наконец, в 4 модуле излагаются классы коллекций и связанные с ними интерфейсы.

Модуль 1. Платформа.NET

Комплексная цель

Понять основную структуру платформы NET, ее эффективность и гибкость. Освоить компиляцию сборок, разобраться со внутренним устройством сборок.

Содержание

Что такое. NET

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

В результате компиляции .NET-программы генерируется не машинный код, а так называемый байт-код, содержащий команды виртуальной машины (в. NET он называется IL-кодом от англ. Intermediate Language - промежуточный язык). Команды байт-кода не зависят от процессора и используемой операционной системы. При запуске программа, содержащая IL-код, подается на вход виртуальной машины, которая и производит выполнение программы. Часть виртуальной машины, называемая JIT-компилятором (Just In Time - непосредственно в данный момент), сразу после запуска. NET-программы переводит ее промежуточный код в машинный (проводя при этом его оптимизацию), после чего запускает программу на исполнение. Если быть точными, то промежуточный код переводится в машинный частями по мере выполнения программы.

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

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

Наиболее "чистым" .NET-языком является C#: он создавался специально для платформы. NET и включает практически все ее возможности. .NET-языки легко взаимодействуют друг с другом не только за счет высокоуровневого промежуточного кода, но и за счет общей системы типов (CTS - Common Type System - общая система типов). Все стандартные типы (строковые, символьные, числовые и логический) имеют одинаковое представление в памяти во всех. NET-языках. Это позволяет, например, создать библиотеку dll на C#, поместить в нее описание класса, а затем воспользоваться этой библиотекой из программы на , сконструировав объект данного класса. Можно также разработать библиотеку на , а потом подключить ее к проекту на Visual . Отметим, что традиционные библиотеки dll не позволяют хранить классы, доступные извне, и обладают рядом других ограничений.

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

Имеются программы, которые могут восстанавливать текст программы по IL-коду (программа Reflector - http://www. /roeder/dotnet/ - восстанавливает код на C#, , Visual , Oxygene, управляемом C++).

Помимо JIT-компилятора, важной частью платформы. NET является набор стандартных библиотек (FCL - Foundation Class Library - общая библиотека классов). Среди них - библиотеки работы с графикой, сетью, базами данных, XML, контейнерами, потоками, содержащие тысячи классов. Каждый. NET-язык может пользоваться всеми возможностями этих библиотек.

Кратко отметим достоинства и недостатки платформы. NET.

Достоинства платформы. NET

1. Платформа .NET поддерживает множество. NET-языков. В их числе C#, Visual , управляемый C++, , Oxygene, Oberon, Zonnon,  .

2. Любой .NET-язык содержит самые современные языковые возможности: классы, свойства, полиморфизм, исключения, перегрузка операций, легкое создание библиотек.

3. .NET-языки легко сочетаются друг с другом, похожи друг на друга по синтаксическим конструкциям и системе типов.

4. Имеется обширная библиотека стандартных классов FCL.

5. .NET-приложения компактны.

6. Платформа .NET активно развивается фирмой Microsoft, добавляются как новые языковые возможности, так и новые библиотеки.

7. Компилятор .NET-языка создать значительно проще, чем компилятор обычного языка.

Недостатки платформы. NET

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

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

3. Сборщик мусора начинает работу в момент исчерпания динамической памяти, его работа занимает несколько миллисекунд. Для приложений реального времени это непозволительно.

4. Запуск .NET-приложения обязательно требует установки на компьютере платформы. NET. Без нее приложение работать не будет (Отметим, что в Windows Vista встроена платформа. NET 3.0).

5. Платформа .NET под другими операционными системами, хотя и существует (например, Mono для Linux), имеет ряд ограничений и несовместимостей.

Основные сокращения

CLR – общеязыковая исполняющая среда, необходимая для загрузки и выполнения программ с NET-кодом.

JIT-compiler (Just In Time компилятор).
IL – промежуточный язык.
FCL – объектно-ориентированная библиотека классов.

Сборки и их исполнение

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

    Заголовок PE – стандартный заголовок PE - файла Windows. Заголовок показывает тип файла: GUI, CUI или DLL. Заголовок CLR – содержит информацию (интерпретируемую CLR и утилитами), которая превращает этот модуль в управляемый. Заголовок включает нужную версию CLR, некоторые флаги, метку метаданных MethodDef, точку входа в управляемый модуль (метод Main), а также месторасположение/размер метаданных модуля, ресурсов, строгого имени, некоторых флагов и пр. Метаданные – каждый управляемый модуль содержит таблицы метаданных. Есть два основных вида таблиц: описывающие типы и члены, определенные в исходном коде, и описывающие типы и члены, на которые имеются ссылки в исходном коде. К метаданным обращаются во время написания программы (функция IntelliSense анализирует метаданные и сообщает, какие методы предлагает тип и какие параметры требуются этим методам) и при компиляции (отпадает необходимость в заголовочных и библиотечных файлах, т. к. компиляторы могут читать метаданные прямо из управляемых модулей). Кроме того, метаданные можно анализировать при выполнении. Декларация (манифест) – один из наборов таблиц в метаданных. Эти таблицы описывают файлы, которые формируют сборку (если сборка многомодульная), общедоступные экспортируемые типы, реализованные в файлах сборки, версию и региональные стандарты сборки, ее издателя, а также файлы ресурсов или данных, включенные в сборку. Также в декларации содержатся ссылки на внешние сборки. Код Intermediate language (IL) – код, создаваемый компилятором при компиляции исходного кода. Впоследствии при запуске сборки составная часть CLR, называемая JIT-компилятором, скомпилирует IL код в команды процессора.

За управление исполнением кода отвечает CLR. Именно поэтому NET-код называется управляемым. При запуске EXE-файла специальная информация из заголовка CLR приводит к загрузке и инициализации CLR. Затем CLR находит метод, являющийся точкой входа приложения, и позволяет приложению начать выполнение. Аналогично, если неуправляемое приложение вызывает LoadLibrary для загрузки управляемой сборки, функция – точка входа DLL загружает CLR чтобы обработать код, содержащийся в сборке.

Исполнение кода сборки

Для выполнения какого-либо метода, его IL-код должен быть преобразован в команды процессора. Этим занимается JIT-компилятор CLR. В качестве примера рассмотрим метод:

static void Main( )

{

Console. WriteLine("First run");

Console. WriteLine("Second run");

}

Непосредственно перед исполнением метода Main CLR находит все типы, на которые ссылается код Main. При этом CLR выделяет внутренние структуры данных, используемые для управления доступом к типам, на которые есть ссылки. В нашем примере метод Main ссылается на единственный тип – Console, и CLR выделяет единственную внутреннюю структуру. Эта внутренняя структура данных содержит по одной записи для каждого метода, определенного в типе. Каждая запись содержит адрес, по которому можно найти реализацию метода. При инициализации этой структуры CLR заносит в каждую запись адрес внутренней недокументированной функции JITCompiler, содержащейся в самой CLR и отвечающей за компиляцию IL-кода вызываемого метода в команды процессора. Когда Main первый раз обращается к WriteLine, вызывается функция JITCompiler. Функции JITCompiler известен вызываемый метод и тип, в котором он определен. JITCompiler ищет в метаданных соответствующей сборки IL-код вызываемого метода. Затем JITCompiler проверяет и компилирует IL-код в команды процессора, которые сохраняются в динамически выделенном блоке памяти. После этого JITCompiler возвращается к внутренней структуре данных типа и заменяет адрес вызываемого метода адресом блока памяти, содержащего собственные команды процессора. В завершение JITCompiler передает управление коду в этом блоке памяти. Этот код – реализация метода WriteLine. Из этого метода управление возвращается в Main, который продолжает нормальную работу. Затем Main обращается к WriteLine вторично. К этому моменту код WriteLine уже проверен и скомпилирован, так что производится обращение к блоку памяти, минуя вызов JITCompiler.

Производительность теряется только при первом вызове метода. Все последующие обращения выполняются «на полной скорости»: повторная верификация и компиляция не производятся.

JIT-компилятор хранит команды процессора в динамической памяти. Это значит, что скомпилированный код уничтожается при завершении приложения. И при следующем запуске (или одновременном запуске двух экземпляров в разных процессах ОС), JIT-компилятор заново будет компилировать IL-код в команды процессора.

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

    JIT-компилятор может обнаружить, что приложение запускается на процессоре Pentium 4 и сгенерировать процессорный код, полностью использующий все преимущества особых команд Pentium 4. JIT-компилятор может обнаружить, что некоторая проверка всегда приводит к отрицательному результату на конкретной машине. CLR может проанализировать выполнение кода и перекомпилировать IL-код в команды процессора при выполнении приложения. Перекомпилированный код может быть реорганизован с учетом обнаруженных некорректных прогнозов ветвления. JIT-компилятор оптимизирует процессорный код аналогично компилятору неуправляемого кода C++.

Кроме того, можно использовать утилиту NGen. exe, поставляемую вместе с. NET Framework SDK. NGen. exe компилирует весь IL-код некоторой сборки в процессорный и сохраняет результирующий код процессора в дисковом файле. При загрузке сборки в период выполнения, CLR автоматически проверяет наличие предварительно скомпилированной версии сборки и, если она есть, загружает скомпилированный код, так что компиляция в период выполнения не производится.

Компиляция приложения с помощью командной строки csc. exe

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

Код библиотеки содержится в файле MyLibrary.cs и имеет вид:

using System;

namespace MyLib

{

public class MyClass

{

public static int add(int a, int b)

{ return a+b; }

}

}

Чтобы скомпилировать эту библиотеку, исполним следующую командную строку:

csc. exe /out:MyLib. dll /t:library MyLibrary. cs

В результате будет создан файл MyLib. dll.

Переключатель /out:MyLib.dll задает имя создаваемого файла. Если указанный переключатель не указан, то создается файл dll с именем, совпадаюим с именем cs-файла. Переключатель /t:library задает тип создаваемого файла (/t:exe – консольное приложение (по умолчанию), /t:winexe – приложение с графическим интерфейсом, /t:library – динамическая библиотека)

Код основной программы содержится в файле MyApplication. cs и имеет вид:

using System;

using MyLib;

class App1

{

static void Main(string[] args)

{

int a, b;

try

{

Console. Write("Введите a: ");

a=int. Parse(Console. ReadLine());

Console. Write("Введите b: ");

b=int. Parse(Console. ReadLine());

Console. WriteLine(a+"+"+b+"="+MyClass. add(a, b));

}

catch (Exception)

{

Console. WriteLine("Ошибка ввода");

}

Console.WriteLine("Программа завершена");

}

}

Чтобы скомпилировать эту программу, исполним следующую командную строку:

csc /t:exe /r:MyLib. dll MyApplication. cs

Переключатель /r:MyLib.dll задает сборку, в которой необходимо вести поиск внешних типов. Если с переключателем /r задано имя файла без указания точного пути, то CSC. exe пытается найти нужную сборку в следующих каталогах:

1. рабочий каталог;

2. каталог, где находится CLR, которую сам компилятор использует для создания результирующей сборки;

3. любые каталоги, заданные переключателем командной строки /lib при вызове CSC. exe;

4. любые каталоги, заданные переменной окружения LIB

Просмотреть содержимое выходного файла можно с помощью утилиты ILDasm. exe – дизассемблерfа языка IL:

ILDasm /Adv MyApplication.exe

В результате запустится ILDasm. exe и загрузится сборка App. exe (переключатель /Adv позволяет сделать доступными некоторые дополнительные элементы меню View).

Проектное задание

Создать сборки dll и exe, приведенные в тексте, используя компилятор командной строки csc.exe. Зафиксировать размеры файлов. Открыть файлы в ILDasm, проанализировать содержимое откомпилированного файла. Открыть в отдельном окне IL-код функции Main, проанализировать команды IL-кода. Откомпилировать оба файла в Visual Studio. Сравнить размеры. Объяснить различие в результатах, используя ILDasm.

Тест рубежного контроля

1. Какая программа предназначена для просмотра откомпилированного NET кода?

a. ILASM. exe

b. ILDASM. exe

c. csc. exe

d. DASM. exe

2. Насколько высока скорость выполнения управляемого кода?

a. Примерно в 100 раз меньше, чем у нативного

b. Примерно в 10 раз меньше, чем у нативного

c. Незначительно меньше, чем у нативного, в некоторых случаях – больше

3. Какая командная строка компилирует библиотеку?

a. csc. exe /t:library MyLibrary. cs

b. csc. exe /out:MyLib. dll /t:dll MyLibrary. cs

c. csc. exe /out:MyLib. dll /t:windll MyLibrary. cs

Модуль 2. Основы языка C#

Комплексная цель

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

Содержание

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

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

В следующем примере в пространство имен CompanyName помещены класс A и пространство имен X:

namespace CompanyName

{

class A //CompanyName. A

{

class B { } //CompanyName. A.B

}

namespace X

{ //CompanyName. X - вложенное пространство имен

class C {} //CompanyName. X.C

}

}

Чтобы воспользоваться именем, используется либо точечная нотация, либо директива using. Например, можно обратиться к классу A внутри пространства имен CompanyName как к CompanyName.A. Кроме того, если подключить пространство имен с помощью директивы using CompanyName, то можно использовать имя A без указания пространства имен. Как видно из данного примера, допускаются вложенные пространства имен: например. обратиться к классу B можно как к CompanyName.A.B. Кроме того, допускаются вложенные классы, которые выступают в этом случае в роли пространств имен, разрешая использовать во вложенном классе те же имена, что и во внешнем. Учитывая внешнее сходство пространств имен и классов, можно считать пространства имен легковесными классами, которые не могут содержать внутри себя ничего кроме классов, интерфейсов и других пространств имен.

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

Тип Object

В CLR каждый объект прямо или косвенно является производным от класса System. Object. Рассмотрим открытые методы System. Object:

    Equals – возвращает true, если два объекта имеют одинаковые значения (подробнее в лекции 4). GetHashCode – возвращает хеш-код значения данного объекта. ToString – по умолчанию возвращает полное имя типа. GetType – возвращает экземпляр объекта Type, который идентифицирует тип объекта. Этот метод невиртуальный, его нельзя изменить, поэтому классу не удается исказить сведения о своем типе. Таков механизм обеспечения контроля типов.

К защищенным методам System. Object (т. е. к тем, которые могут вызываться в методах потомков) относятся:

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

Оператор new

CLR требует, чтобы все объекты создавались с помощью оператора new (который порождает IL-команду newobj). Оператор new делает следующее:

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

Выполнив все эти операции, new возвращает ссылку на вновь созданный объект.

У оператора new нет пары – оператора delete, т. е. нет явного способа освобождения памяти, занятой объектом. Сборкой мусора занимается CLR, которая в некоторый момент обнаруживает, что объект больше не используется или недоступен, и автоматически освобождает память, им занимаемую.

Приведение типов

CLR разрешает неявно привести тип объекта к его собственному типу или любому из его базовых типов. Для приведения типа к производному от него типу разработчик на C# должен использовать операцию явного приведения типов – неявное преобразование приведет к ошибке. Например:

class Employee {...}

class App

{

public static void Main()

{

Object o = new Employee();

Employee e = (Employee) o;

}

}

При попытке неверного приведения генерируется исключение System. InvalidCastException.

Приведение типов с помощью операций is и as

Операция is проверяет совместимость объекта с данным типом, а в качестве результата выдает значение true или false. Эта операция никогда не генерирует исключение. Если ссылка на объект равна null, оператор is всегда возвращает false, так как нет объекта, для которого нужно определить тип.

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

Способ 1 использования операций is и as:

if (o is Student)

{ Student s = (Student)o; }

Способ 2 использования операций is и as:

Student s = o as Student;

if (s ! = null)

{ }

Второй код эффективнее, так как в первом случае CLR проверяет совместимость с типом Student дважды: при выполнении операции is и в теле оператора if (при приведении типа). Во втором же случае CLR проверяет совместимость o с типом Student только раз, а if лишь сравнивает s с null – такая проверка намного эффективнее, чем определение типа объекта.

Элементарные типы

Типы данных, которые поддерживаются компилятором напрямую, называются элементарными типами и отображаются им в типы из библиотеки классов. NET Framework Class Library (FCL).

Типы FCL и соответствующие элементарные типы C#:

Элементарный тип C#

Тип FCL

Описание

sbyte

System. SByte

8-разрядное значение со знаком

byte

te

8-разрядное значение без знака

short

System. Int16

16-разрядное значение со знаком.

ushort

System. UInt16

16-разрядное значение без знака.

int

System. Int32

32-разрядное значение со знаком

uint

System. UInt32

32-разрядное значение без знака

long

System. Int64

64-разрадное значение со знаком

ulong

System. UInt64

64-разрядное значение без знака

char

System. Char

16-разрядный символ Unicode

float

System. Single

32-разрядное float в стандарте IEEE

double

System. Double

64-разрядное float в стандарте IEEE

bool

System. Boolean

Значение true или false

decimal

System. Decimal

128-разрядное значение с плавающей точкой повышенной точности.

object

System. Object

базовый тип для всех типов

string

System. String

массив символов

К элементарным типам применима литеральная форма записи:

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4