Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЭУМК ОСиСПч3 май.doc
Скачиваний:
8
Добавлен:
03.05.2019
Размер:
1.2 Mб
Скачать

Тема 3. Система типов и объектная модель среды .Net

Общая система типов данных в среде .NET

Common Type System (CTS) — часть .NET Framework, формальная спецификация, определяющая, как какой-либо тип (класс, интерфейс, структура, встроенный тип данных) должен быть определён для его правильного выполнения средой .NET. Кроме того, данный стандарт определяет, как определения типов и специальные значения типов представлены в компьютерной памяти. Целью разработки CTS было обеспечение возможности программам, написанным на различных языках программирования, легко обмениваться информацией. Как это принято в языках программирования, тип может быть описан как определение набора допустимых значений (например, «все целые от 0 до 10») и допустимых операций над этими значениями (например, сложение и вычитание).

IL-язык и CTS позволяют языкам программирования .NET, компиляторы для которых могут генерировать код на IL-языке, использовать библиотеку классов .NET Framework.

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

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

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

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

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

  • Определение правил для видимости типов и доступа к членам типа;

  • Определение правил, управляющие наследованием типов, виртуальными методами и продолжительностью существования объектов.

Объектная модель среды .NET

Одной из особенностей среды .Net Framework является то что все объекты .Net являются производными от одного базового класса— системного класса Object.

Таким образом, все что выполняется в среде .Net Framework имеет свой тип, значение которого может использовать CLR для своей работы.

Тип в среде CRL сдержит следующие элементы:

  • Поля (элементы данных);

  • Методы;

  • Свойства;

  • События.

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

Языки программирования для платформы .NET

Существует ряд языков программирования, которые называют языки программирования .NET или CLI-языки (Common Language Infrastructure). Эти языки программирования используются для создания приложений под платформу .NET Framework. За исключением нескольких замечаний, большинство CLI-языков целиком компилируются в Common Intermediate Language (CIL), промежуточный язык, который может быть оттранслирован непосредственно в машинный код при помощи виртуальной машины Common Language Runtime (CLR), являющуюся частью  .NET Framework, Mono и Portable.NET.

Таким образом не имеет значения на каком из языков программирования .NET ведется разработка – все равно во время выполнения в среде CLR её CIL-код компилируется и кэшируется на лету в машинный код, соответствующий архитектуре, на которой выполняется программа.

Среды разработки, поддерживающие разработку приложения для .NET Framework:

  • Microsoft Visual Studio;

  • SharpDevelop;

  • MonoDevelop;

  • Borland Developer Studio;

  • Zonnon;

  • PascalABC.NET.

Краткий список языков программирования .NET и их описание:

Название языка

Год выхода

Предок

Краткое описание

#Smalltalk

2003

Smalltalk

Компилятор позволяет использовать классы обычных .NET-программ. Фактически, большинство Smalltalk-классов стандарта ANSI, поддерживаемых #Smalltalk, являются лишь обёртками вокруг стандартных .NET-классов.

A#

2005

Ада

Свободно распространяется Департаментом Информатики при Военно-воздушной академии США.

B#

2009

С, C++, Java

Сильно урезанная версия языка C# (иногда описывается как гибрид языков C++ и Java), спроектированная специально для встраиваемых программируемых систем.

C++/CLI

2005

C++

Версия языка C++, включающая в себя расширения для поддержки объектов CLR. Реализация существует только для платформы .NET Framework. Код может компилироваться в основанный на CIL управляемый код либо смешанный код, объединяющий как управляемый код, так и естественный код.

C# (ECMA 334)

2000

C++

Самый широко используемый CLI-язык, схож с языками Java, Delphi и C++. Реализации языка поддерживаются платформами .NET Framework, Portable.NET и Mono.

CIL/MSIL/IL

1999

языки ассемблера

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

Delphi

2005

Паскаль

Delphi.NET Delphi Prism

DotLisp

2003

Лисп

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

F#

OCaml

Мультипарадигмальный CLI-язык, поддерживающий как функциональную, так и императивную парадигмы объектно-ориентированного программирования. Является вариантом языка ML и обладает большой совместимостью с OCaml. Компилятор поддерживается корпорацией Microsoft.

Perl.NET

Perl

Ruby.NET

Ruby

VB.NET

Visual Basic

Полностью перепроектированная объектно-ориентированная версия Visual Basic. Реализована на .NET Framework и Mono.

Zonnon

2003

Паскаль,Модула-2 и Оберон

Наследник языка Модула-2, расширенный средствами сборки мусора, объектного программирования, параллельного программирования (мультипрограммирования), переопределения операторов и обработки исключений. Изначально создавался для платформы .NET.

Размерные и ссылочные типы данных

При использовании типов данных в разработке на языке программирования С# нужно помнить что С# является типизированным языком. При объявлении переменной необходимо указывать тип объекта и компилятор поможет избежать ошибок, связанных с присвоением переменным значений только того типа, который им соответствует. Тип объекта указывает компилятору размер объекта (например, объект типа short занимает в памяти 2 байта) и его свойства (например, для формы - форма может быть видима и невидима, и т.д.). 

Типы данных в С# можно разделить на:

  • Встроенные и пользовательские типы данных (по функциональным возможностям);

  • Размерные и ссылочные типы данных (по размещению переменных в памяти).

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

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

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

Объекты размерного типа поддерживают два вида синтаксиса:

  • Статическое объявление (int x; Point point;)

  • Динамическое объявление (int x = new int(); Point point = new Point();)

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

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

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

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

Информация о числовых типах содержится в таблице 3.1.

Таблица 3.1 Числовые типы языка C#

Размер

(бит)

Имя

типа

Диапазон/Точность

8

sbyte

–128...127

16

short

–32 768...32 767

32

int

–2 147 483 648...2 147 483 647

64

long

-9 223 372 036 854 775 808...9 223 372 036 854 775 807

8

byte

0...255

16

ushort

0...65535

32

uint

0...4294967295

64

ulong

0...18446744073709551615

32

float

Точность: от 1.5 × 10−45 до 3.4 × 1038, 7 цифр

64

double

Точность: от 5.0 × 10−324 до 1.7 × 10308, 15 цифр

128

decimal

Точность: от 1.0 × 10−28 до 7.9 × 1028, 28 цифр

В языке C# нет базовых типов - все типы реализуются как классы библиотеки NET Framework. Однако язык C# имеет набор встроенных типов, которые рассматриваются как псевдонимы типов в пространстве имен System. Например, тип string - это псевдоним типа System.String, а тип int - псевдоним типа System.Int32.

C# поддерживает следующие встроенные типы:

  • bool - псевдоним класса System.Boolean;

  • byte - псевдоним класса System.Byte;

  • sbyte - псевдоним класса System.SByte;

  • char - псевдоним класса System.Char;

  • decimal - псевдоним класса System.Decimal;

  • float - псевдоним класса System.Single

  • double - псевдоним класса System.Double;

  • int - псевдоним класса System.Int32;

  • uint - псевдоним класса System.UInt32;

  • long - псевдоним класса System.Int64;

  • ulong - псевдоним класса System.UInt64;

  • object - псевдоним класса System.Object;

  • short - псевдоним класса System.Int16;

  • ushort - псевдоним класса System.UInt16;

  • string - псевдоним класса System.String.

Все встроенные типы подразделены на группы:

  • целочисленные типы (int, long, ulong);

  • вещественные типы (float, double);

  • логический тип (bool);

  • символьные типы (char, string);

  • объектный тип (object).

Типы sbyte, ushort, uint, ulong не соответствуют Common Language Specification. Это означает, что данные типы не следует использовать в интерфейсах многоязыковых приложений и библиотек.

Тип decimal удобен для проведения финансовых вычислений.

Тип bool служит для представления булевых значений. Переменные данного типа могут принимать значения true или false.

При работе с символами и строками в C# используется кодировка Unicode.

Тип char представляет символ в 16-битной Unicode-кодировке, тип string – это последовательность Unicode-символов. Однако, несмотря на то, что тип string относится к примитивным, переменная этого типа хранит адрес строки в динамической памяти.

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

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

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

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

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

5. Перечисление – тип, содержащий в качестве членов именованные целочисленные константы.

6. Делегат – пользовательский тип для представления ссылок на методы.

Класс

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

В среде .NET функциональность классов стала больше и они могут содержать:

  • поля (fields), аналогичные членам-переменным в C++;

  • методы (methods), аналогичные членам-функциям в C++;

  • свойства (properties), предоставляющие доступ к данным так же, как и поля, но реализованные с применением аксессоров (get и set);

  • события (events), определяющие уведомления, которые способен распознавать класс.

Ниже представлен пример класса на языке С#, реализующий тип данных Rectangle :

В классе Rectangle определено 7 членов: 2 поля, 3 свойства и 2 метода, причем оба являются конструкторами. Поля являются защищенными, т.е. доступ к ним имеют только сам класс Rectangle и его производные. Чтобы прочитать или записать ширину или высот)- прямоугольника, представленного объектом класса Rectangle, клиент должен задействовать свойства Width и Height. Аксессоры set этих свойств генерируют исключение, если указывается неверное значение. Такая защита была бы невозможна, если б высота и ширина объекта Rectangle задавались бы через открытые поля. Area является свойством, которое можно только читать, потому что у него нет аксессора set. При попытке установить значение свойства Area компилятор выдаст ошибку.

Важно отметить, что ни в С#, ни в каком-либо другом языке для .NET нет оператора delete. Разработчик создает объект, а уничтожает его сборщик мусора.

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

Все классы наследуют виртуальный метод Finalize класса System.Object — исходного корневого класса для всех типов данных. Метод Finalize вызывается прямо перед тем, как сборщик мусора уничтожит объект. Сборщик мусора освобождает занимаемую объектом память, но те классы, которые содержат описатели файлов, окон и другие неуправляемые ресурсы («неуправляемые» — потому что их не освобождает сборщик мусора), должны переопределять метод Finalize и с его помощью освобождать эти ресурсы. Это тоже имеет важные для разработчиков последствия.

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

Структура

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

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

Примерами простых типов данных служат целые числа и байты.

Именно поэтому в .NET Framework поддерживаются как ссылочные, так и размерные типы. В С# размерные пользовательские типы определяются ключевым словом struct. У размерных типов издержки меньше, чем у ссылочных, потому что они размещаются в стеке, а не в куче. К размерным типам относятся байты, целые и большинство других встроенных типов данных, поддерживаемых CLR.

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

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

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

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

Интерфейсы

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

Интерфейс задает определенное соглашение между типом и его пользователями. Так, многие классы в пространстве имен System.Collections порождены от интерфейса /Enumerable. В нем определены методы итерации по элементам набора (collection). Поскольку классы-наборы FCL реализуют IEnumerable, их можно указывать в конструкции foreach языка С#. Во время исполнения сгенерированный из foreach код применяет метод GetEnumerator интерфейса IEnumerable, чтобы организовать итерацию по содержимому набора.

Интерфейсы определяются ключевым словом interface языка С#.

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

Для выяснения, реализует ли объект заданный интерфейс, в С# служит ключевое слово is. Пусть объект msg реализует интерфейс ISecret, тогда в этом примере is возвратит значение true; иначе вернется false.

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

Перечисления

Перечисления в .NET Framework подобны перечислениям в C++. Они являются типами, состоящими из множества именованных констант и в С# определяются ключевым словом enum. Вот пример простого перечислимого типа Color:

Во многих FCL-классах перечислимые типы служат в качестве параметров методов. Например, если для синтаксического разбора текста применяется класс Regex и при этом желательно, чтобы при анализе не учитывался регистр, конструктору Regex передается не числовое значение, а член перечислимого типа RegexOptions:

Regex regex = new Regex (exp, RegexOptions.IgnoreCase);

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

1 и т. д.).

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

Делегаты

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

Обычно делегаты служат для определения сигнатур методов обратного вызова, которые осуществляют реакцию на событие. К примеру. FCL-класс Timer (член пространства имен System.Timers) определяет событие Elapsed, которое наступает по истечении заданного интервала таймера. Реагирующие на событие Elapsed приложения передают объекту Timer ссылку на метод, который следует вызвать при наступлении этого события. Передаваемая «ссылка» — это не просто адрес памяти, а экземпляр делегата — оболочка адреса этого метода. Именно с этой целью в пространстве имен System.Timers определен делегат ElapsedEventHandler.

Примерный исходный код класса Timer:

Timer инициирует событие Elapsed:

А так клиент мог бы использовать объект Timer, чтобы вызывать метод UpdateData каждые 60 секунд:

Из вышеприведенного кода, UpdateData соответствует сигнатуре, заданной делегатом. Чтобы зарегистрироваться для получения событий Elapsed, клиент создает новый экземпляр ElapsedEventHandler, который обертывает метод UpdateData (ссылка на UpdateData была передана конструктору ElapsedEventHandler) и связывает его с событием Elapsed объекта timer оператором +=, Этот подход применяется в приложениях для .NET Framework повсеместно.

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

На практике полезно знать о том, что происходит, когда компилятор встречает определение делегата. Допустим, компилятору С# встретился такой код:

Его ответной реакцией является создание класса, производного от System.MulticastDelegate. В этом случае ключевое слово delegate служит просто синонимом следующего:

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

Упаковка и распаковка размерных типов данных в среде .NET

В C# допускается рассмотрение значений размерных типов как переменных типа Object. Преобразование в объект называется операцией упаковки (boxing), обратное преобразование – операцией распаковки (unboxing).

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

int a = 5;

object refValue = a;

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

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

int b = 5;

b = (int)refValue;

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

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

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

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

Динамические массивы в среде .NET и языке C#

Объявление массива в языке C# схоже с объявлением переменной, но после указания типа размещается пара квадратных скобок – признак массива:

int[] Data;

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

int[] Data;

Data = new int[10];

Созданный массив автоматически заполняется значениями по умолчанию своего базового типа. Создание массива можно совместить с его объявлением:

int[] Data = new int[10];

Для доступа к элементу массива указывается имя массива и индекс в квадратных скобках: Data[0] = 10.

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

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

Инициализация может быть выполнена в развернутой и короткой форме, которые эквивалентны:

int[] Data = new int[10] {1, 2, 3, 5, 7, 11, 13, 17, 19, 23};

int[] Data = {1, 2, 3, 5, 7, 11, 13, 17, 19, 23};

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

При необходимости можно объявить массивы, имеющие несколько размерностей. Для этого в квадратных скобках после типа массива помещают запятые, «разделяющие» размерности:

// объявлен двумерный массив D

int[,] D;

// создаем массив D так:

D = new int[10,2];

// объявим трехмерный Cube и создадим его

int[,,] Cube = new int[3,2,5];

// установим элемент массива Cube:

Cube[1,1,0] = 1000;

// объявим маленький двумерный массив и инициализируем его

int[,] C = new int[2,4] {

{1, 2, 3, 4},

{10, 20, 30, 40}

};

// то же самое, немного короче:

int[,] C = {{1, 2, 3, 4}, {10, 20, 30, 40}};

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

int[][] Table; // Table –массив одномерных массивов

Table = new int[3][]; // в Table будет 3 одномерных массива

Table[0] = new int[2]; // в первом будет 2 элемента

Table[1] = new int[20]; // во втором – 20 элементов

Table[2] = new int[12]; // в третьем – 12 элементов

// а вот так мы работаем с элементами массива Table:

Table[1][3] = 1000;

// совместим объявление и инициализацию массива массивов

int[][] T = {

new int[2] {10, 20},

new int[3] {1, 2, 3}

};

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

int[] Data = {1,3,5,7,9};

int Sum = 0;

foreach(int element in Data)

Sum += element;

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

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

using System;

class MainClass

{

public static void Main()

{

Console.Write("Введите число элементов: ");

int Size = Int32.Parse(Console.ReadLine());

int[] M = new int[Size];

for (int i = 0; i < Size; i++) {

Console.Write("Введите {0} элемент массива: ", i);

M[i] = Int32.Parse(Console.ReadLine());

}

Console.WriteLine("Исходный массив:");

foreach(int i in M) Console.Write("{0,6}", i);

Console.WriteLine();

for(int i = 0; i < Size-1; i++)

for(int j = i+1; j < Size; j++) {

if (M[i] > M[j]) {

int dop = M[i];

M[i] = M[j];

M[j] = dop;

}

}

Console.WriteLine("Отсортированный массив:");

foreach(int i in M) Console.Write("{0,6}", i);

}

}

Все массивы в .NET Framework могут рассматриваться как классы, являющиеся потомками класса System.Array. В табл. 3 описаны основные методы и свойства класса System.Array.

Таблица 3 Элементы класса System.Array

Имя элемента

Описание

Rank

Свойство только для чтения, возвращает размерность массива

Length

Свойство только для чтения, возвращает число элементов массива

GetLength()

Метод возвращает число элементов в указанном измерении

GetLowerBound()

Метод возвращает нижнюю границу для указанного измерения

GetUpperBound()

Метод возвращает верхнюю границу для указанного измерения

GetValue()

Метод возвращает значение элемента с указанными индексами

SetValue()

Метод устанавливает значение элемента с указанными индексами (значение – первый аргумент).

Sort()

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

BinarySearch()

Статический метод поиска элемента в отсортированном массиве. Тип элемента массива должен реализовывать интерфейс IComparable

IndexOf()

Статический метод, возвращает индекс первого вхождения своего аргумента в одномерный массив или –1, если элемента в массиве нет

LastIndexOf()

Статический метод. Возвращает индекс последнего вхождения своего аргумента в одномерный массив или –1, если элемента в массиве нет

Reverse()

Статический метод, меняет порядок элементов в одномерном массиве или его части на противоположный

Copy()

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

Clear()

Статический метод. Устанавливает для диапазона элементов массива значение по умолчанию для типов элементов

CreateInstance()

Статический метод. Динамически создает экземпляр массива любого типа, размерности и длины

Теперь рассмотрим примеры использования описанных методов и свойств. В примерах выводимые данные записаны как комментарии. Вначале – использование нескольких простых элементов System.Array:

int[,] M = {{1, 3, 5}, {10, 20, 30}};

Console.WriteLine(M.Rank); // 2

Console.WriteLine(M.Length); // 6

Console.WriteLine(M.GetLowerBound(0)); // 0

Console.WriteLine(M.GetUpperBound(1)); // 2

Продемонстрируем сортировку и поиск в одномерном массиве:

int[] M = {1, -3, 5, 10, 2, 5, 30};

Console.WriteLine(Array.IndexOf(M, 5)); ); //2

Console.WriteLine(Array.LastIndexOf(M, 5)); //5

Array.Reverse(M);

foreach(int a in M)

Console.WriteLine(a); //30, 5, 2, 10, 5, -3, 1

Array.Sort(M);

foreach(int a in M)

Console.WriteLine(a); //-3, 1, 2, 5, 5, 10, 30

Console.WriteLine(Array.BinarySearch(M, 10)); //5

Опишем процесс динамического создания массива. Данный способ позволяет задать для массивов произвольные нижние и верхние границы. Допустим, нам необходим двумерный массив из элементов decimal, первая размерность которого представляет годы в диапазоне от 1995 до 2004, а вторая – кварталы в диапазоне от 1 до 4. Следующий код осуществляет создание массива и обращение к элементу массива:

//Назначение этих массивов понятно из их названий

int[] LowerBounds = {1995, 1};

int[] Lengths = {10, 4};

//"Заготовка" для будущего массива

decimal[,] Target;

Target = (decimal[,])Array.CreateInstance(typeof(decimal),

Lengths,LowerBounds);

//Пример обращения к элементу

Target[2000, 1] = 10.3M;

Допустимо было написать код для создания массива без приведения типов. Однако в этом случае для установки и чтения элементов необходимо было бы использовать методы SetValue() и GetValue():

Array Target;

Target = Array.CreateInstance(typeof(decimal), Lengths, LowerBounds);

Target.SetValue(10.3M, 2000, 1);

Console.WriteLine(Target.GetValue(2000, 1));

Работа с элементами массива, созданного при помощи CreateInstance(), происходит медленнее, чем работа с «обычным» массивом.