Предыдущая лекция |   | Следующая лекция :----------------:|:----------:|:----------------: [Строки.](./4_prog_string.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Общие сведения о подпрограммах.](./t5_function.md) # Перечисления. Множества. Работа с датами. Кортежи. * [Перечисления](#перечисления-enum) * [Кортежи](#кортежи-tuple) * [Множества](#множества) * [Работа с датами и временем](#работа-с-датами-и-временем) ## Перечисления (enum) Кроме примитивных типов данных в **C#** есть такой тип как **enum** или **перечисление**. Перечисления представляют набор логически связанных констант. Объявление перечисления происходит с помощью оператора **enum**. Далее идет название перечисления, после которого указывается тип перечисления - он обязательно должен представлять целочисленный тип (**byte**, **int**, **short**, **long**). Если тип явным образом не указан, то **по умолчанию используется тип int**. Затем идет список элементов перечисления через запятую: ```cs enum Days { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } ``` ```cs enum Time : byte { Morning, Afternoon, Evening, Night } ``` В примерах выше каждому элементу перечисления присваивается целочисленное значение, причем первый элемент будет иметь значение `0`, второй `1` и так далее. Мы можем также явным образом указать значения элементов, либо указав значение первого элемента: ```cs enum Operation { Add = 1, // каждый следующий элемент по умолчанию увеличивается на единицу Subtract, // этот элемент равен 2 Multiply, // равен 3 Divide // равен 4 } ``` Но можно и для всех элементов явным образом указать значения: ```cs enum Operation { Add = 2, Subtract = 4, Multiply = 8, Divide = 16 } ``` При этом константы перечисления могут иметь одинаковые значения, либо даже можно присваивать одной константе значение другой константы: ```cs enum Color { White = 1, Black = 2, Green = 2, Blue = White // Blue = 1 } ``` Каждое перечисление фактически определяет новый тип данных. Затем в программе мы можем определить переменную этого типа и использовать ее: ```cs enum Operation { Add = 1, Subtract, Multiply, Divide } Operation op; op = Operation.Add; Console.WriteLine(op); // Add Console.ReadLine(); ``` В программе мы можем присвоить значение этой переменной. При этом в качестве её значения должна выступать одна из констант, определенных в данном перечислении. То есть несмотря на то, что каждая константа сопоставляется с определенным числом, мы не можем присвоить ей числовое значение, например, `Operation op = 1;`. И также если мы будем выводить на консоль значение этой переменной, то мы получим имя константы, а не числовое значение. Если же необходимо получить числовое значение, то следует выполнить приведение к числовому типу: ```cs Operation op; op = Operation.Multiply; Console.WriteLine((int) op); // 3 ^^^^^ ``` Зачастую переменная перечисления выступает в качестве хранилища состояния, в зависимости от которого производятся некоторые действия. Так, рассмотрим применение перечисления на более реальном примере: ```cs enum Operation { Add = 1, Subtract, Multiply, Divide } // Тип операции задаем с помощью константы Operation.Add MathOp(10, 5, Operation.Add); // Тип операции задаем с помощью константы Operation.Multiply MathOp(11, 5, Operation.Multiply); Console.ReadLine(); static void MathOp(double x, double y, Operation op) { double result = 0.0; switch (op) { case Operation.Add: result = x + y; break; case Operation.Subtract: result = x - y; break; case Operation.Multiply: result = x * y; break; case Operation.Divide: result = x / y; break; } Console.WriteLine("Результат операции равен {0}", result); } ``` --- ## Кортежи (Tuple) Кортежи предоставляют удобный способ для работы с набором значений. Кортеж представляет набор значений, заключенных в круглые скобки: ```cs var tuple = (5, 10); ``` В данном случае определен кортеж *tuple*, который имеет два значения: `5` и `10`. В дальнейшем мы можем обращаться к каждому из этих значений через поля с названиями `Item+<порядковый_номер_поля_в_кортеже>`. Например: ```cs var tuple = (5, 10); Console.WriteLine(tuple.Item1); // 5 Console.WriteLine(tuple.Item2); // 10 tuple.Item1 += 26; Console.WriteLine(tuple.Item1); // 31 Console.Read(); ``` В данном случае тип определяется неявно. Но мы также можем явным образом указать для переменной кортежа тип: ```cs (int, int) tuple = (5, 10); ``` Так как кортеж содержит два числа, то в определении типа нам надо указать два числовых типа. Или другой пример определения кортежа: ```cs (string, int, double) person = ("Tom", 25, 81.23); ``` Первый элемент кортежа в данном случае представляет строку, второй элемент - тип **int**, а третий - тип **double**. Мы также можем дать названия полям кортежа: ```cs var tuple = (count:5, sum:10); ^^^^^ ^^^ Console.WriteLine(tuple.count); // 5 Console.WriteLine(tuple.sum); // 10 ``` Теперь чтобы обратиться к полям кортежа используются их имена, а не названия _Item1_ и _Item2_. Мы даже можем не использовать переменную для определения всего кортежа, а использовать отдельные переменные для его полей: ```cs var (name, age) = ("Tom", 23); Console.WriteLine(name); // Tom Console.WriteLine(age); // 23 Console.Read(); ``` ### Использование кортежей Кортежи могут передаваться в качестве параметров в метод, могут быть возвращаемым результатом функции, либо использоваться иным образом. Например, одной из распространенных ситуаций является возвращение из функции двух и более значений, в то время как функция можно возвращать только одно значение. И кортежи представляют оптимальный способ для решения этой задачи: ```cs var tuple = GetValues(); Console.WriteLine(tuple.Item1); // 1 Console.WriteLine(tuple.Item2); // 3 Console.Read(); private static (int, int) GetValues() { var result = (1, 3); return result; } ``` Здесь определен метод _GetValues_, который возвращает кортеж. Кортеж определяется как набор значений, помещенных в круглые скобки. И в данном случае мы возвращаем кортеж из двух элементов типа **int**, то есть два числа. Другой пример: ```cs var tuple = GetNamedValues(new int[]{ 1,2,3,4,5,6,7}); Console.WriteLine(tuple.count); Console.WriteLine(tuple.sum); Console.Read(); private static (int sum, int count) GetNamedValues(int[] numbers) { var result = (sum:0, count: 0); for (int i=0; i < numbers.Length; i++) { result.sum += numbers[i]; result.count++; } return result; } ``` И также кортеж может передаваться в качестве параметра в метод: ```cs var (name, age) = GetTuple(("Tom", 23), 12); Console.WriteLine(name); // Tom Console.WriteLine(age); // 35 Console.Read(); private static (string name, int age) GetTuple((string n, int a) tuple, int x) { var result = (name: tuple.n, age: tuple.a + x); return result; } ``` ## Множества Коллекция, содержащая только уникальные элементы, называется множеством (**set**). В составе **.NET** имеются два множества — **HashSet``** и **SortedSet``**. Оба они реализуют интерфейс **ISet``**. Класс **HashSet``** содержит неупорядоченный список различающихся элементов, а в **SortedSet``** элементы упорядочены. Интерфейс **ISet``** предоставляет методы для **создания**, **объединения** нескольких множеств, **пересечения** множеств и определения, является ли одно множество надмножеством или подмножеством другого. Ниже перечислены наиболее употребительные конструкторы, определенные в классе **HashSet``**: * `public HashSet ()` создается пустое множество * `public HashSet(IEnumerable collection)` создается множество, состоящее из элементов указываемой коллекции _collection_ * `public HashSet(IEqualityCompare comparer)` допускается указывать способ сравнения с помощью параметра comparer * `public HashSet(IEnumerable collection, IEqualityCompare comparer)` создается множество, состоящее из элементов указываемой коллекции collection, и используется заданный способ сравнения comparer. Имеется также пятая форма конструктора данного класса, в которой допускается инициализировать множество последовательно упорядоченными данными. В этом классе предоставляется также метод _RemoveWhere_, удаляющий из множества элементы, удовлетворяющие заданному условию, или предикату. Помимо свойств, определенных в интерфейсах, которые реализуются в классе **HashSet``**, в него введено дополнительное свойство _Comparer_, приведенное ниже: ```cs public IEqualityComparer Comparer { get; } ``` Оно позволяет получать метод сравнения для вызывающего хеш-множества. Ниже перечислены четыре наиболее часто используемых конструкторов, определенных в классе SortedSet: * `public SortedSet()` создается пустое множество * `public SortedSet(IEnumerable collection)` создается множество, состоящее из элементов указываемой коллекции collection * `public SortedSet(IComparer comparer)` допускается указывать способ сравнения с помощью параметра comparer * `public SortedSet(IEnumerable collection, IComparer comparer)` создается множество, состоящее из элементов указываемой коллекции collection, и используется заданный способ сравнения comparer Имеется также пятая форма конструктора данного класса, в которой допускается инициализировать множество последовательно упорядоченными данными. В этом классе предоставляется также метод _GetViewBetween_, возвращающий часть множества в форме объекта типа **SortedSet``**, метод _RemoveWhere_, удаляющий из множества элементы, не удовлетворяющие заданному условию, или предикату, а также метод _Reverse_, возвращающий объект типа **IEnumerable``**, который циклически проходит множество в обратном порядке. Помимо свойств, определенных в интерфейсах, которые реализуются в классе **SortedSet``**, в него введены дополнительные свойства, приведенные ниже: * `public IComparer Comparer { get; }` * `public T Max { get; }` * `public T Min { get; }` Свойство _Comparer_ получает способ сравнения для вызывающего множества. Свойство _Мах_ получает наибольшее значение во множестве, а свойство _Min_ — наименьшее значение во множестве. Давайте рассмотрим пример использования множеств: ```cs // Создадим два множества SortedSet ss = new SortedSet(); SortedSet ss1 = new SortedSet(); ss.Add('A'); ss.Add('B'); ss.Add('C'); ss.Add('Z'); ShowColl(ss, "Первая коллекция: "); ss1.Add('X'); ss1.Add('Y'); ss1.Add('Z'); ShowColl(ss1, "Вторая коллекция"); ss.SymmetricExceptWith(ss1); ShowColl(ss,"Исключили разноименность (одинаковые элементы) двух множеств: "); ss.UnionWith(ss1); ShowColl(ss, "Объединение множеств: "); ss.ExceptWith(ss1); ShowColl(ss, "Вычитание множеств"); Console.ReadLine(); static void ShowColl(SortedSet ss, string s) { Console.WriteLine(s); foreach (char ch in ss) Console.Write(ch + " "); Console.WriteLine("\n"); } ``` Самописанный пример, объединяющий работу перечислений и множеств: ```cs enum Days { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } HashSet DaysHashSet = new HashSet(); DaysHashSet.Add(Days.Friday); DaysHashSet.Add(Days.Friday); DaysHashSet.Add(Days.Monday); foreach (Days DayItem in DaysHashSet) Console.WriteLine(DayItem); Console.Write("Press ENTER to continue..."); Console.ReadLine(); ``` Программа выведет только `Friday` и `Monday`. Т.е. дубли в множество не добавляются и при этом никаких ошибок не возникает. Такое поведение может быть полезно при отслеживании состояния какого-то устройства. Периодически опрашивая устройство мы можем устанавливать состояния не проверя установлено уже это состояние или нет. Для коллекций вообще и для множеств в частности есть методы объединения, пересечения и разности. ### Разность последовательностей С помощью метода **Except** можно получить разность двух последовательностей: ```cs var DaysHashSet1 = new HashSet() { Days.Friday, Days.Monday }; var DaysHashSet2 = new HashSet() { Days.Friday, Days.Sunday }; var ResHashSet = DaysHashSet1.Except(DaysHashSet2); foreach (Days DayItem in ResHashSet) Console.WriteLine(DayItem); Console.Write("Press ENTER to continue..."); Console.ReadLine(); ``` В данном случае из множества *DaysHashSet1* убираются все элементы, которые есть в массиве *DaysHashSet2*. Результатом операции будет один элемент: ``` Monday ``` ### Пересечение последовательностей Для получения пересечения последовательностей, то есть общих для обоих наборов элементов, применяется метод _Intersect_: ```cs string[] soft = { "Microsoft", "Google", "Apple"}; string[] hard = { "Apple", "IBM", "Samsung"}; // пересечение последовательностей var result = soft.Intersect(hard); foreach (string s in result) Console.WriteLine(s); ``` Так как оба набора имеют только один общий элемент, то соответственно только он и попадет в результирующую выборку: ``` Apple ``` ### Объединение последовательностей Для объединения двух последовательностей используется метод **Union**. Его результатом является новый набор, в котором имеются элементы, как из первой, так и из второй последовательности. Повторяющиеся элементы добавляются в результат только один раз: ```cs string[] soft = { "Microsoft", "Google", "Apple"}; string[] hard = { "Apple", "IBM", "Samsung"}; // объединение последовательностей var result = soft.Union(hard); foreach (string s in result) Console.WriteLine(s); ``` Результатом операции будет следующий набор: ``` Microsoft Google Apple IBM Samsung ``` Если же нам нужно простое объединение двух наборов, то мы можем использовать метод **Concat**: ```cs var result = soft.Concat(hard); ``` Те элементы, которые встречаются в обоих наборах, дублируются. ### Удаление дубликатов Для удаления дублей в наборе используется метод **Distinct**: ```cs var result = soft.Concat(hard).Distinct(); ``` Последовательное применение методов *Concat* и *Distinct* будет подобно действию метода *Union*. --- ## Работа с датами и временем Для работы с датами и временем в **.NET** предназначена структура **DateTime**. Она представляет дату и время от `00:00:00 1 января 0001 года` до `23:59:59 31 декабря 9999` года. Для создания нового объекта **DateTime** также можно использовать конструктор. Пустой конструктор создает начальную дату: ```cs DateTime date1 = new DateTime(); Console.WriteLine(date1); // 01.01.0001 0:00:00 ``` То есть мы получим минимально возможное значение, которое также можно получить следующим образом: ```cs Console.WriteLine(DateTime.MinValue); ``` Чтобы задать конкретную дату, нужно использовать один из конструкторов, принимающих параметры: ```cs DateTime date1 = new DateTime(2015, 7, 20); // год - месяц - день Console.WriteLine(date1); // 20.07.2015 0:00:00 ``` Установка времени: ```cs DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // год - месяц - день - час - минута - секунда Console.WriteLine(date1); // 20.07.2015 18:30:25 ``` Если необходимо получить текущую время и дату, то можно использовать ряд свойств **DateTime**: ```cs Console.WriteLine(DateTime.Now); Console.WriteLine(DateTime.UtcNow); Console.WriteLine(DateTime.Today); ``` Консольный вывод: ``` 20.07.2015 11:43:33 20.07.2015 8:43:33 20.07.2015 0:00:00 ``` Свойство *DateTime.Now* возвращает текущие дату и время компьютера (локальные), *DateTime.UtcNow* - дату и время относительно времени по Гринвичу (GMT) и *DateTime.Today* - только текущую дату (время сброшено в ноль). При работе с датами надо учитывать, что по умолчанию для представления дат применяется григорианский календарь. Но что будет, если мы захотим получить день недели для `5 октября 1582` года: ```cs DateTime someDate = new DateTime(1582, 10, 5); Console.WriteLine(someDate.DayOfWeek); ``` Консоль выстветит значение `Tuesday`, то есть вторник. Однако, как может быть известно из истории, впервые переход с юлианского календаря на григорианский состоялся в октябре 1582 года. Тогда после даты 4 октября (четверг) (еще по юлианскому календарю) сразу перешли к 15 октября (пятница)(уже по григорианскому календарю). Таким образом, фактически выкинули 10 дней. То есть после 4 октября шло 15 октября. В большинстве случаев данный факт вряд ли как-то повлияет на вычисления, однако при работе с очень давними датами данный аспект следует учитывать. ### Операции с DateTime Основные операции со структурой **DateTime** связаны со сложением или вычитанием дат. Например, надо к некоторой дате прибавить или, наоборот, отнять несколько дней. Для добавления дат используется ряд методов: * `Add(TimeSpan value)`: добавляет к дате значение типа **TimeSpan** * `AddDays(double value)`: добавляет к текущей дате несколько дней * `AddHours(double value)`: добавляет к текущей дате несколько часов * `AddMinutes(double value)`: добавляет к текущей дате несколько минут * `AddMonths(int value)`: добавляет к текущей дате несколько месяцев * `AddYears(int value)`: добавляет к текущей дате несколько лет Например, добавим к некоторой дате 3 часа: ```cs DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // 20.07.2015 18:30:25 Console.WriteLine(date1.AddHours(3)); // 20.07.2015 21:30:25 ``` Для вычитания дат используется метод `Substract(DateTime date)`: ```cs DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // 20.07.2015 18:30:25 DateTime date2 = new DateTime(2015, 7, 20, 15, 30, 25); // 20.07.2015 15:30:25 Console.WriteLine(date1.Subtract(date2)); // 03:00:00 ``` Здесь даты различаются на три часа, поэтому результатом будет **TimeSpan** "03:00:00". Метод **Substract** не имеет возможностей для отдельного вычитания дней, часов и так далее. Но это и не надо, так как мы можем передавать в метод _AddDays_ и другие методы добавления отрицательные значения: ```cs // вычтем три часа DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // 20.07.2015 18:30:25 Console.WriteLine(date1.AddHours(-3)); // 20.07.2015 15:30:25 ``` ### Форматирование даты Кроме операций сложения и вычитания еще есть ряд методов форматирования дат: ```cs DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); Console.WriteLine(date1.ToLocalTime()); // 20.07.2015 21:30:25 Console.WriteLine(date1.ToUniversalTime()); // 20.07.2015 15:30:25 Console.WriteLine(date1.ToLongDateString()); // 20 июля 2015 г. Console.WriteLine(date1.ToShortDateString()); // 20.07.2015 Console.WriteLine(date1.ToLongTimeString()); // 18:30:25 Console.WriteLine(date1.ToShortTimeString()); // 18:30 ``` Метод _ToLocalTime_ преобразует время **UTC** в локальное время, добавляя смещение относительно времени по Гринвичу. Метод _ToUniversalTime_, наоборот, преобразует локальное время во время **UTC**, то есть вычитает смещение относительно времени по Гринвичу. Остальные методы преобразуют дату к определенному формату. Метод _ToString_ тоже можно использовать для форматирования. По умолчанию для русской культурной среды он возвращает дату в формате `ДД.ММ.ГГГГ Ч:ММ:СС`. Но можно задать нужный формат в параметрах: `дата.ToString("dd.MM.yyyy HH:mm:ss");` Описатель | Описание :---:|---- d | Представляет день месяца от 1 до 31. Одноразрядные числа используются без нуля в начале dd | Представляет день месяца от 1 до 31. К одноразрядным числам в начале добавляется ноль ddd | Сокращенное название дня недели (пн,вт...) dddd | Полное название дня недели (понедельник, вторник...) f / fffffff | Представляет миллисекунды. Количество символов f указывает на число разрядов в миллисекундах g | Представляет период или эру (например, "н. э.") h | Часы в виде от 1 до 12. Часы с одной цифрой не дополняются нулем hh | Часы в виде от 01 до 12. Часы с одной цифрой дополняются нулем H | Часы в виде от 0 до 23. Часы с одной цифрой не дополняются нулем HH | Часы в виде от 0 до 23. Часы с одной цифрой дополняются нулем K | Часовой пояс m | Минуты от 0 до 59. Минуты с одной цифрой не дополняются начальным нулем mm | Минуты от 0 до 59. Минуты с одной цифрой дополняются начальным нулем M | Месяц в виде от 1 до 12 MM | Месяц в виде от 1 до 12. Месяц с одной цифрой дополняется начальным нулем MMM | Сокращенное название месяца MMMM | Полное название месяца s | Секунды в виде числа от 0 до 59. Секунды с одной цифрой не дополняются начальным нулем ss | Секунды в виде числа от 0 до 59. Секунды с одной цифрой дополняются начальным нулем t | Первые символы в обозначениях AM и PM tt | AM или PM y | Представляет год как число из одной или двух цифр. Если год имеет более двух цифр, то в результате отображаются только две младшие цифры yy | Представляет год как число из одной или двух цифр. Если год имеет более двух цифр, то в результате отображаются только две младшие цифры. Если год имеет одну цифру, то он дополняется начальным нулем yyy | Год из трех цифр yyyy | Год из четырех цифр yyyyy | Год из пяти цифр. Если в году меньше пяти цифр, то он дополняется начальными нулями z | Представляет смещение в часах относительно времени UTC zz | Представляет смещение в часах относительно времени UTC. Если смещение представляет одну цифру, то она дополняется начальным нулем. --- ## Контрольные вопросы 1. [Перечисления](#перечисления-enum) 1. [Кортежи](#кортежи-tuple) 1. [Множества](#множества) 1. [Дата и время](#работа-с-датами-и-временем) Предыдущая лекция |   | Следующая лекция :----------------:|:----------:|:----------------: [Строки.](./4_prog_string.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Общие сведения о подпрограммах.](./t5_function.md)