Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Операторы и операции языка.](./t3l1_2.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Строки.](./4_prog_string.md)

<!-- https://professorweb.ru/my/csharp/charp_theory/level4/4_1.php -->

# Основы языка `C#`

**Содержание:**

* [Массивы](#массивы)
* [Многомерные массивы](#многомерные-массивы)
* [Класс Array](#класс-array)
* [Массивы в качестве параметров](#массивы-в-качестве-параметров)
* [Коллекции](#коллекции)
    * [ArrayList](#arraylist)
    * [List](#list)
    * [Dictionary](#dictionary)

## Массивы

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

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

Для тoго чтобы воспользоваться массивом в программе, требуется двухэтапная процедура, поскольку в **C#** массивы реализованы в виде объектов. Во-первых, необходимо объявить переменную, которая может обращаться к массиву. Например, команда `int[] arrayOfInteger` объявляет переменную с именем *ArrayOfInteger* с типом **int[]** - одномерный массив целых чисел. И во-вторых, нужно создать экземпляр массива, используя оператор **new**.

```cs
// Объявляем массив
int[] myArr = new int[5];

// Инициализируем каждый элемент массива вручную
myArr[0] = 100;
myArr[1] = 23;
myArr[2] = 25;
myArr[3] = 31;
myArr[4] = 1;

foreach (int i in myArr)
    Console.WriteLine(i);

Console.ReadLine();
```

Следует иметь в виду, что если массив только объявляется, но явно не инициализируется, каждый его элемент будет установлен в значение, принятое по умолчанию для соответствующего типа данных (например, элементы массива типа **bool** будут устанавливаться в `false`, а элементы массива типа **int** — в `0`).

### Инициализация массива

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

```cs
// Синтаксис инициализации массива с использованием
// ключевого слова new
int[] myArr = new int[] {10,20,30,40,50};

// Синтаксис инициализации массива без использования
// ключевого слова new
string[] info = { "Фамилия", "Имя", "Отчество" };

// Используем ключевое слово new и желаемый размер
char[] symbol = new char[4] { 'X','Y','Z','M' };
```

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

### Неявно типизированные массивы

Ключевое слово **var** позволяет определить переменную так, чтобы лежащий в ее основе тип выводился компилятором. Аналогичным образом можно также определять неявно типизированные локальные массивы. С использованием такого подхода можно определить новую переменную массива без указания типа элементов, содержащихся в массиве. Давайте рассмотрим пример:

```cs
var arr1 = new[] { 1, 2, 3 };

Console.WriteLine(
    "Тип массива arr1 - {0}",
    arr1.GetType());

var arr2 = new[] { "One", "Two", "Three" };

Console.WriteLine(
    "Тип массива arr2 - {0}",
    arr2.GetType());

Console.ReadLine();
```

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

### Определение массива объектов

В большинстве случаев при определении массива тип элемента, содержащегося в массиве, указывается явно. Хотя на первый взгляд это выглядит довольно понятно, существует одна важная особенность. В основе каждого типа в системе типов **.NET** (в том числе фундаментальных типов данных) в конечном итоге лежит базовый класс **System.Object**. В результате получается, что в случае определения массива объектов находящиеся внутри него элементы могут представлять собой что угодно:

```cs
// Объявляем и инициализируем массив объектов
object[] arrByObject = { true, 10, "Привет", 13.7m };

// Выведем в консоль тип каждого члена массива
foreach (object me in arrByObject)
    Console.WriteLine(
        "Тип {0} - {1}",
        me, me.GetType());

Console.ReadLine();
```

![](../img/04001.png)

### Свойство Length

Реализация в **C#** массивов в виде объектов дает целый ряд преимуществ. Одно из них заключается в том, что с каждым массивом связано свойство **Length**, содержащее число элементов, из которых может состоять массив. Следовательно, у каждого массива имеется специальное свойство, позволяющее определить его длину.

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

```cs
int[] myArr = { 1, 2, 3, 4 };

for (int i = 0; i < myArr.Length; i++)
    Console.WriteLine(myArr[i]);

Console.ReadLine();
```

## Многомерные массивы

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

### Двумерные массивы

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

```cs
// Объявляем двумерный массив
int[,] myArr = new int[4, 5];

Random ran = new Random();

// Инициализируем данный массив
for (int i = 0; i < 4; i++)
{
    for (int j = 0; j < 5; j++)
    {
       myArr[i, j] = ran.Next(1, 15);
       Console.Write("{0}\t", myArr[i, j]);
    }
    Console.WriteLine();
}
```

![](../img/04002.png)

Обратите особое внимание на способ объявления двумерного массива. Схематическое представление массива myArr показано ниже:

![](../img/04003.png)

Если вам приходилось раньше программировать на **С**, **С++** или **Java**, то будьте особенно внимательны, объявляя или организуя доступ к многомерным массивам в **C#**. В этих языках программирования размеры массива и индексы указываются в отдельных квадратных скобках, тогда как в **C#** они разделяются запятой.

### Массивы трех и более измерений

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

```
тип[,...,] имя_массива = new тип[размер1, размер2, ... размеры];
```

Ниже приведен пример программы, использующей трехмерный массив:

```cs
int[,,] myArr = new int[5,5,5];

for (int i = 0; i < 5; i++)
   for (int j = 0; j < 5; j++)
      for (int k = 0; k < 5; k++)
         myArr[i, j, k] = i + j + k;
```

### Инициализация многомерных массивов

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

```
тип[,] имя_массива = {
    {val, val, val, ..., val},
    {val, val, val, ..., val},
    {val, val, val, ..., val}
};
```

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

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

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

## Ступенчатые (зубчатые) массивы

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

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

```
тип [][] имя массива = new тип[размер] [];
```

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

Давайте рассмотрим пример использования ступенчатого массива:

```cs
int i = 0;
// Объявляем ступенчатый массив
int[][] myArr = new int[4][];
myArr[0] = new int[4];
myArr[1] = new int[6];
myArr[2] = new int[3];
myArr[3] = new int[4];

// Инициализируем ступенчатый массив
for (; i < 4; i++)
{
    myArr[0][i] = i;
    Console.Write("{0}\t",myArr[0][i]);
}

Console.WriteLine();
for (i = 0; i < 6; i++)
{
    myArr[1][i] = i;
    Console.Write("{0}\t", myArr[1][i]);
}

Console.WriteLine();
for (i = 0; i < 3; i++)
{
    myArr[2][i] = i;
    Console.Write("{0}\t", myArr[2][i]);
}

Console.WriteLine();
for (i = 0; i < 4; i++)
{
    myArr[3][i] = i;
    Console.Write("{0}\t", myArr[3][i]);
}

Console.ReadLine();
```

Созданный массив в данном примере можно структурно представить в виде:

![](../img/04004.png)

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

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

### Применение свойства *Length* при обращении со ступенчатыми массивами

Особый случай представляет применение свойства **Length** при обращении со ступенчатыми массивами. В этом случае с помощью данного свойства можно получить длину каждого массива, составляющего ступенчатый массив. Давайте рассмотрим следующий пример:

```cs
int[][] myArr = new int[3][];
myArr[0] = new int[4];
myArr[1] = new int[10];
myArr[2] = new int[1];

Console.WriteLine("Общая длина всего массива: " + myArr.Length);
Console.WriteLine("\nДлина первой цепи: " + myArr[0].Length);
Console.WriteLine("\nДлина второй цепи: " + myArr[1].Length);
Console.WriteLine("\nДлина третьей цепи: " + myArr[2].Length);

Console.ReadLine();
```

![](../img/04005.png)


Обратите особое внимание на то, как свойство *Length* используется в ступенчатом массиве *myArr*. Напомним, что двумерный ступенчатый массив представляет собой массив массивов. Следовательно, когда используется выражение `myArr.Length` то в нем определяется число массивов, хранящихся в массиве _myArr_ (в данном случае — 3 массива). А для получения длины любого отдельного массива, составляющего ступенчатый массив, служит выражение `myArr[0].Length`.

## Класс Array

Каждый создаваемый массив получает большую часть функциональности от класса **System.Array**. Общие члены этого класса позволяют работать с массивом с использованием полноценной объектной модели. Таким образом, методы и свойства, определенные в классе **Array**, можно использовать с любым массивом **C#**. Давайте разберем некоторые полезные особенности класса **Array**.

### Создание массивов

Класс **Array** является абстрактным, поэтому создать массив с использованием какого-либо конструктора нельзя. Однако вместо применения синтаксиса **C#** для создания экземпляров массивов также возможно создавать их с помощью статического метода *CreateInstance()*. Это исключительно удобно, когда заранее неизвестен тип элементов массива, поскольку тип можно передать методу *CreateInstance()* в параметре как объект **Type**:

```cs
// Создаем массив типа string, длиной 5
Array myArr = Array.CreateInstance(typeof(string),5);

// Инициализируем первые два поля массива
myArr.SetValue("Name",0);
myArr.SetValue("Age",1);

// Считываем данные из массива
string s = (string)myArr.GetValue(1);

Console.ReadLine();
```

Обратите внимание, что для установки значений в массив служит метод *SetValue*, а для их чтения — метод *GetValue*.

### Копирование массивов

Поскольку массивы — это ссылочные типы, присваивание переменной типа массива другой переменной создает две переменных, ссылающихся на один и тот же массив. Для копирования массивов предусмотрена реализация массивами интерфейса **ICloneable**. Метод *Clone*, определенный в этом интерфейсе, создает неглубокую копию массива. Если элементы массива относятся к типу значений, то все они копируются, если массив содержит элементы ссылочных типов, то сами эти элементы не копируются, а копируются лишь ссылки на них.

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

```cs
string[] arr2 = new string[5];
// Создаем массив типа string, длиной 5
Array myArr = Array.CreateInstance(typeof(string),5);

// Инициализируем первые три поля массива
myArr.SetValue("Name",0);
myArr.SetValue("Age",1);
myArr.SetValue("Adress",2);

// Копируем массив с помощью метода Clone()
string[] arr1 = (string[])myArr.Clone();

foreach (string s in arr1)
    Console.Write("\t"+s);

// Копируем с помощью метода Copy()
Console.WriteLine();
Array.Copy(myArr, arr2, myArr.Length);
foreach (string s in arr2)
    Console.Write("\t"+s);

Console.ReadLine();
```

### Сортировка и поиск

В классе **Array** реализован алгоритм быстрой сортировки (**Quicksort**) элементов массива. Метод *Sort* требует от элементов реализации интерфейса **IComparable**. Простые типы, такие как **System.String** и **System.Int32**, реализуют **IComparable**, так что можно сортировать элементы, относящиеся к этим типам.

C помощью разных вариантов метода *Sort* можно отсортировать массив полностью или в заданных пределах либо отсортировать два массива, содержащих соответствующие пары "ключ-значение". После сортировки в массиве можно осуществить эффективный поиск, используя разные варианты метода *BinarySearch*.

```cs
int[] myArr = { 4, 5, -183, 12, 34, 0, 2 ,-13 };

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

foreach (int x in myArr)
    Console.Write("\t{0}",x);

// Реализуем сортировку массива
Console.WriteLine(
    "\n\nОтсортированный массив:");

Array.Sort(myArr);

foreach (int x in myArr)
    Console.Write("\t{0}",x);

// Организуем поиск числа 12
Console.WriteLine("\n\nПоиск числа:");

int search = Array.BinarySearch(myArr, 12);

Console.WriteLine(
    "Число 12 находится на {0} позиции",
    search+1);

Console.ReadLine();
```

![](../img/04006.png)

## Массивы в качестве параметров

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

```cs
foreach (string s in myReturn())
    Console.WriteLine(s);

int[] mySortArray = {4,1,2,5,3};

for (int i = 0; i <= 4; i++)
    mySortArray[i] = mySort(mySortArray, i);

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

foreach (int i in mySortArray)
    Console.Write("\t"+i);

Console.ReadLine();

// Данный метод возвращает массив Info
static Array myReturn()
{
    string[] Info = {"Name","Family","Adress" };
    return Info;
}

// Передача массива в метод в качестве параметра
// Данный метод возвращает значение одного из индексов
// отсортированного массива
static int mySort(Array sortArray, int i)
{
    Array.Sort(sortArray);
    return (int)sortArray.GetValue(i);
}
```

Результат работы данной программы:

![](../img/04007.png)

### Структура ArraySegment`<T>`

Структура **ArraySegment`<T>`** представляет сегмент массива. Эта структура может применяться, когда нужно вернуть или передать методу части массива. Вместо передачи в метод массива, смещения и счетчика в отдельных параметрах, можно передать единственный параметр **ArraySegment`<T>`**. В этой структуре информация о сегменте (смещение и счетчик) заключена непосредственно в ее членах:

```cs
int[] arr1 = { 1, 2, 3, 4, 5, 6 };
int[] arr2 = { 7, 8, 9, 10 };

var mySegmentsArray = new ArraySegment<int>[3]
{
    // Инициализируем сегменты массивов
    new ArraySegment<int>(arr1,0,2),
    new ArraySegment<int>(arr2,0,1),
    new ArraySegment<int>(arr1,1,2)
};

Console.WriteLine("Сумма выбранных значений равна:\n"+SumArraySegments(mySegmentsArray));
Console.ReadLine();

// Метод, вычисляющий сумму выбранных сегментов
static int SumArraySegments(ArraySegment<int>[] value)
{
    int sum = 0;
    foreach (var s in value)
        for (int i = s.Offset; i < s.Offset + s.Count; i++)
            sum += s.Array[i];
    return sum;
}
```

Важно отметить, что сегменты массива не копируют элементы исходного массива. Вместо этого через **ArraySegment`<T>`** можно получить доступ к исходному массиву. Если изменяются элементы сегмента, то эти изменения будут видны в исходном массиве.

<!-- TODO дописать про кортежи (в отдельной лекции про типы данных) -->

## Коллекции 

<!-- https://metanit.com/sharp/tutorial/4.3.php -->

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

Большая часть классов коллекций содержится в пространствах имен **System.Collections** (простые необобщенные классы коллекций), **System.Collections.Generic** (обобщенные или типизированные классы коллекций) и **System.Collections.Specialized** (специальные классы коллекций). Также для обеспечения параллельного выполнения задач и многопоточного доступа применяются классы коллекций из пространства имен **System.Collections.Concurrent**.

### ArrayList

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

**Основные методы класса:**

* `int Add(object value)`: добавляет в список объект *value*

* `void AddRange(ICollection col)`: добавляет в список объекты коллекции *col*, которая представляет интерфейс ICollection - интерфейс, реализуемый коллекциями.

* `void Clear()`: удаляет из списка все элементы

* `bool Contains(object value)`: проверяет, содержится ли в списке объект *value*. Если содержится, возвращает true, иначе возвращает false

* `void CopyTo(Array array)`: копирует текущий список в массив array.

* `ArrayList GetRange(int index, int count)`: возвращает новый список *ArrayList*, который содержит *count* элементов текущего списка, начиная с индекса *index*

* `int IndexOf(object value)`: возвращает индекс элемента *value*

* `void Insert(int index, object value)`: добавляет в список по индексу *index* объект *value*

* `void InsertRange(int index, ICollection col)`: добавляет в список начиная с индекса index коллекцию ICollection

* `int LastIndexOf(object value)`: возвращает индекс последнего вхождения в списке объекта value

* `void Remove(object value)`: удаляет из списка объект value

* `void RemoveAt(int index)`: удаляет из списка элемент по индексу index

* `void RemoveRange(int index, int count)`: удаляет из списка count элементов, начиная с индекса index

* `void Reverse()`: переворачивает список

* `void SetRange(int index, ICollection col)`: копирует в список элементы коллекции col, начиная с индекса index

* `void Sort()`: сортирует коллекцию

Кроме того, с помощью свойства *Count* можно получить количество элементов в списке.

Посмотрим применение класса **ArrayList** на примере.

```cs
ArrayList list = new ArrayList();
list.Add(2.3); // заносим в список объект типа double
list.Add(55); // заносим в список объект типа int
list.AddRange(new string[] { "Hello", "world" }); // заносим в список строковый массив

// перебор значений
foreach (object o in list)
{
    Console.WriteLine(o);
}

// удаляем первый элемент
list.RemoveAt(0);
// переворачиваем список
list.Reverse();
// получение элемента по индексу
Console.WriteLine(list[0]);
// перебор значений
for (int i = 0; i < list.Count; i++)
{
    Console.WriteLine(list[i]);
}

Console.ReadLine();
```

Во-первых, так как класс **ArrayList** находится в пространстве имен **System.Collections**, то подключаем его (`using System.Collections;`).

Вначале создаем объект коллекции через конструктор как объект любого другого класса: `ArrayList list = new ArrayList();`. При необходимости мы могли бы так же, как и с массивами, выполнить начальную инициализацию коллекции, например, `ArrayList list = new ArrayList(){1, 2, 5, "string", 7.7};`

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

Через цикл *foreach* мы можем пройтись по всем объектам списка. И поскольку данная коллекция хранит разнородные объекты, а не только числа или строки, то в качестве типа перебираемых объектов выбран тип object: `foreach (object o in list)`

Многие коллекции, в том числе и *ArrayList*, реализуют удаление с помощью методов *Remove/RemoveAt*. В данном случае мы удаляем первый элемент, передавая в метод RemoveAt индекс удаляемого элемента.

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

С помощью индекса мы можем получить элемент коллекции так же, как и в массивах: `object firstObj = list[0];`

### List

Класс **List`<T>`** из пространства имен **System.Collections.Generic** представляет простейший список однотипных объектов (в угловых скобках как раз записывается тип объектов).

Среди его методов можно выделить следующие:

* `void Add(T item)`: добавление нового элемента в конец списка

* `void AddRange(ICollection collection)`: добавление в список коллекции или массива

* `int BinarySearch(T item)`: бинарный поиск элемента в списке. Если элемент найден, то метод возвращает индекс этого элемента в коллекции. При этом список ***должен быть отсортирован***.

* `int IndexOf(T item)`: возвращает индекс первого вхождения элемента в списке

* `void Insert(int index, T item)`: вставляет элемент item в списке на позицию index

* `bool Remove(T item)`: удаляет элемент item из списка, и если удаление прошло успешно, то возвращает true

* `void RemoveAt(int index)`: удаление элемента по указанному индексу index

* `void Sort()`: сортировка списка

Посмотрим реализацию списка на примере:

```cs
List<int> numbers = new List<int>() { 1, 2, 3, 45 };
numbers.Add(6); // добавление элемента

numbers.AddRange(new int[] { 7, 8, 9 });

numbers.Insert(0, 666); // вставляем на первое место в списке число 666

numbers.RemoveAt(1); //  удаляем второй элемент

foreach (int i in numbers)
{
    Console.WriteLine(i);
}

List<Person> people = new List<Person>(3);
people.Add(new Person() { Name = "Том" });
people.Add(new Person() { Name = "Билл" });

foreach (Person p in people)
{
    Console.WriteLine(p.Name);
}

Console.ReadLine();

class Person
{
    public string Name { get; set; }
}
```

Здесь у нас создаются два списка: один для объектов типа **int**, а другой - для объектов **Person**. В первом случае мы выполняем начальную инициализацию списка: `List<int> numbers = new List<int>() { 1, 2, 3, 45 };`

Во втором случае мы используем другой конструктор, в который передаем начальную емкость списка: `List<Person> people = new List<Person>(3);`. Указание начальной емкости списка (capacity) позволяет в будущем увеличить производительность и уменьшить издержки на выделение памяти при добавлении элементов. Также начальную емкость можно установить с помощью свойства *Capacity*, которое имеется у класса **List**.

### Dictionary

Еще один распространенный тип коллекции представляют **словари**. Словарь хранит объекты, которые представляют пару ключ-значение. Каждый такой объект является объектом структуры **KeyValuePair`<TKey, TValue>`**. Благодаря свойствам *Key* и *Value*, которые есть у данной структуры, мы можем получить ключ и значение элемента в словаре.

Рассмотрим на примере использование словарей:

```cs
Dictionary<int, string> countries = new Dictionary<int, string>(5);
countries.Add(1, "Russia");
countries.Add(3, "Great Britain");
countries.Add(2, "USA");
countries.Add(4, "France");
countries.Add(5, "China");          
 
foreach (KeyValuePair<int, string> keyValue in countries)
{
    Console.WriteLine(keyValue.Key + " - " + keyValue.Value);
}
 
// получение элемента по ключу
string country = countries[4];
// изменение объекта
countries[4] = "Spain";
// удаление по ключу
countries.Remove(2);
```

Класс словарей также, как и другие коллекции, предоставляет методы *Add* и *Remove* для добавления и удаления элементов. Только в случае словарей в метод *Add* передаются два параметра: ключ и значение. А метод *Remove* удаляет не по индексу, а по ключу.

Так как в нашем примере ключами является объекты типа int, а значениями - объекты типа string, то словарь в нашем случае будет хранить объекты **KeyValuePair`<int, string>`**. В цикле **foreach** мы их можем получить и извлечь из них ключ и значение.

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

```cs
Dictionary<char, Person> people = new Dictionary<char, Person>();
people.Add('b', new Person() { Name = "Bill" });
people.Add('t', new Person() { Name = "Tom" }); 
people.Add('j', new Person() { Name = "John" });
 
foreach (KeyValuePair<char, Person> keyValue in people)
{
    // keyValue.Value представляет класс Person
    Console.WriteLine(keyValue.Key + " - " + keyValue.Value.Name); 
}
 
// перебор ключей
foreach (char c in people.Keys)
{
    Console.WriteLine(c);
}
 
// перебор по значениям
foreach (Person p in people.Values)
{
    Console.WriteLine(p.Name);
}
```

Здесь в качестве ключей выступают объекты типа **char**, а значениями - объекты **Person**. Используя свойство *Keys*, мы можем получить ключи словаря, а свойство _Values_ соответственно хранит все значения в словаре.

Для добавления необязательно применять метод `Add`, можно использовать сокращенный вариант:

```cs
Dictionary<char, Person> people = new Dictionary<char, Person>();
people.Add('b', new Person() { Name = "Bill" });
people['a'] = new Person() { Name = "Alice" };
```

Несмотря на то, что изначально в словаре нет ключа `a` и соответствующего ему элемента, то он все равно будет установлен. Если же он есть, то элемент по ключу `a` будет заменен на новый объект `new Person() { Name = "Alice" }`

**Инициализация словарей**

Если в **C#** 5.0 мы могли инициализировать словари следующим образом:

```cs
Dictionary<string, string> countries = new Dictionary<string, string>
{
    {"Франция", "Париж"},
    {"Германия", "Берлин"},
    {"Великобритания", "Лондон"}
};
 
foreach(var pair in countries)
    Console.WriteLine("{0} - {1}", pair.Key, pair.Value);
```

То начиная с **C#** 6.0 доступен также еще один способ инициализации:

```cs
Dictionary<string, string> countries = new Dictionary<string, string>
{
    ["Франция"] = "Париж",
    ["Германия"] = "Берлин",
    ["Великобритания"] = "Лондон"
}; 
```

---

## Контрольные вопросы

1. [Массивы](#массивы)
1. [Коллекции](#коллекции)

Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Операторы и операции языка.](./t3l1_2.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Строки.](./4_prog_string.md)
