Предыдущая лекция |   | Следующая лекция :----------------:|:----------:|:----------------: [Строки.](./articles/4_prog_string.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Общие сведения о подпрограммах.](./articles/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 } class Program { static void Main(string[] args) { 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 class Program { enum Operation { Add = 1, Subtract, Multiply, Divide } 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); } static void Main(string[] args) { // Тип операции задаем с помощью константы Operation.Add MathOp(10, 5, Operation.Add); // Тип операции задаем с помощью константы Operation.Multiply MathOp(11, 5, Operation.Multiply); Console.ReadLine(); } } ``` --- ## Кортежи (**Tuple**) Кортежи предоставляют удобный способ для работы с набором значений. Кортеж представляет набор значений, заключенных в круглые скобки: ```cs var tuple = (5, 10); ``` В данном случае определен кортеж *tuple*, который имеет два значения: 5 и 10. В дальнейшем мы можем обращаться к каждому из этих значений через поля с названиями `Item[порядковый_номер_поля_в_кортеже]`. Например: ```cs static void Main(string[] args) { 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 static void Main(string[] args) { var (name, age) = ("Tom", 23); Console.WriteLine(name); // Tom Console.WriteLine(age); // 23 Console.Read(); } ``` ### Использование кортежей Кортежи могут передаваться в качестве параметров в метод, могут быть возвращаемым результатом функции, либо использоваться иным образом. Например, одной из распространенных ситуаций является возвращение из функции двух и более значений, в то время как функция можно возвращать только одно значение. И кортежи представляют оптимальный способ для решения этой задачи: ```cs static void Main(string[] args) { 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 static void Main(string[] args) { 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 static void Main(string[] args) { 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) * public HashSet(IEqualityCompare comparer) * public HashSet(IEnumerable collection, IEqualityCompare comparer) В первой форме конструктора создается пустое множество, а во второй форме — множество, состоящее из элементов указываемой коллекции collection. В третьей форме конструктора допускается указывать способ сравнения с помощью параметра comparer. А в четвертой форме создается множество, состоящее из элементов указываемой коллекции collection, и используется заданный способ сравнения comparer. Имеется также пятая форма конструктора данного класса, в которой допускается инициализировать множество последовательно упорядоченными данными. В этом классе предоставляется также метод RemoveWhere(), удаляющий из множества элементы, удовлетворяющие заданному условию, или предикату. Помимо свойств, определенных в интерфейсах, которые реализуются в классе HashSet, в него введено дополнительное свойство Comparer, приведенное ниже: ```cs public IEqualityComparer Comparer { get; } ``` Оно позволяет получать метод сравнения для вызывающего хеш-множества. Ниже перечислены четыре наиболее часто используемых конструкторов, определенных в классе SortedSet: * public SortedSet() * public SortedSet(IEnumerable collection) * public SortedSet(IComparer comparer) * public SortedSet(IEnumerable collection, IComparer comparer) В первой форме конструктора создается пустое множество, а во второй форме — множество, состоящее из элементов указываемой коллекции collection. В третьей форме конструктора допускается указывать способ сравнения с помощью параметра comparer. А в четвертой форме создается множество, состоящее из элементов указываемой коллекции collection, и используется заданный способ сравнения comparer. Имеется также пятая форма конструктора данного класса, в которой допускается инициализировать множество последовательно упорядоченными данными. В этом классе предоставляется также метод GetViewBetween(), возвращающий часть множества в форме объекта типа SortedSet, метод RemoveWhere(), удаляющий из множества элементы, не удовлетворяющие заданному условию, или предикату, а также метод Reverse(), возвращающий объект типа IEnumerable, который циклически проходит множество в обратном порядке. Помимо свойств, определенных в интерфейсах, которые реализуются в классе SortedSet, в него введены дополнительные свойства, приведенные ниже: * public IComparer Comparer { get; } * public T Max { get; } * public T Min { get; } Свойство Comparer получает способ сравнения для вызывающего множества. Свойство Мах получает наибольшее значение во множестве, а свойство Min — наименьшее значение во множестве. Давайте рассмотрим пример использования множеств: ```cs using System; using System.Collections.Generic; namespace ConsoleApplication1 { class Program { static void Main() { // Создадим два множества 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 class Program { enum Days { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } static void Main(string[] args) { 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 ``` Здесь даты различаются на три часа, поэтому результатом будет дата "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. Если смещение представляет одну цифру, то она дополняется начальным нулем. --- Предыдущая лекция |   | Следующая лекция :----------------:|:----------:|:----------------: [Строки.](./articles/4_prog_string.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Общие сведения о подпрограммах.](./articles/t5_function.md)