Перечисления. Множества. Работа с датами. Кортежи.

January 20, 2024 · View on GitHub

Предыдущая лекция Следующая лекция
Строки.СодержаниеОбщие сведения о подпрограммах.

Перечисления. Множества. Работа с датами. Кортежи.

Перечисления (enum)

Кроме примитивных типов данных в C# есть такой тип как enum или перечисление. Перечисления представляют набор логически связанных констант. Объявление перечисления происходит с помощью оператора enum. Далее идет название перечисления, после которого указывается тип перечисления - он обязательно должен представлять целочисленный тип (byte, int, short, long). Если тип явным образом не указан, то по умолчанию используется тип int. Затем идет список элементов перечисления через запятую:

enum Days
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}
enum Time : byte
{
    Morning,
    Afternoon,
    Evening,
    Night
}

В примерах выше каждому элементу перечисления присваивается целочисленное значение, причем первый элемент будет иметь значение 0, второй 1 и так далее. Мы можем также явным образом указать значения элементов, либо указав значение первого элемента:

enum Operation
{ 
    Add = 1,   // каждый следующий элемент по умолчанию увеличивается на единицу
    Subtract, // этот элемент равен 2
    Multiply, // равен 3
    Divide    // равен 4
}

Но можно и для всех элементов явным образом указать значения:

enum Operation
{ 
    Add = 2,
    Subtract = 4,
    Multiply = 8,
    Divide = 16
}

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

enum Color
{
    White = 1,
    Black = 2,
    Green = 2,
    Blue = White // Blue = 1
}

Каждое перечисление фактически определяет новый тип данных. Затем в программе мы можем определить переменную этого типа и использовать ее:

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;. И также если мы будем выводить на консоль значение этой переменной, то мы получим имя константы, а не числовое значение. Если же необходимо получить числовое значение, то следует выполнить приведение к числовому типу:

Operation op;
op = Operation.Multiply;
Console.WriteLine((int)op); // 3

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

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)

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

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

var tuple = (5, 10);

В данном случае определен кортеж tuple, который имеет два значения: 5 и 10. В дальнейшем мы можем обращаться к каждому из этих значений через поля с названиями Item[порядковый_номер_поля_в_кортеже]. Например:

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();
}

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

(int, int) tuple = (5, 10);

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

(string, int, double) person = ("Tom", 25, 81.23);

Первый элемент кортежа в данном случае представляет строку, второй элемент - тип int, а третий - тип double.

Мы также можем дать названия полям кортежа:

var tuple = (count:5, sum:10);
Console.WriteLine(tuple.count); // 5
Console.WriteLine(tuple.sum); // 10

Теперь чтобы обратиться к полям кортежа используются их имена, а не названия Item1 и Item2.

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

static void Main(string[] args)
{
    var (name, age) = ("Tom", 23);
    Console.WriteLine(name);    // Tom
    Console.WriteLine(age);     // 23
    Console.Read();
}

Использование кортежей

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

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

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, то есть два числа.

Другой пример:

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;
}

И также кортеж может передаваться в качестве параметра в метод:

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, приведенное ниже:

public IEqualityComparer<T> 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 — наименьшее значение во множестве. Давайте рассмотрим пример использования множеств:

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            // Создадим два множества
            SortedSet<char> ss = new SortedSet<char>();
            SortedSet<char> ss1 = new SortedSet<char>();

            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<char> ss, string s)
        {
            Console.WriteLine(s);
            foreach (char ch in ss)
                Console.Write(ch + " ");
            Console.WriteLine("\n");
        }
    }
}

Самописанный пример, объединяющий работу перечислений и множеств:

class Program
{
    enum Days
    {
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday
    }

    static void Main(string[] args)
    {
        HashSet<Days> DaysHashSet = new HashSet<Days>();
        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 можно получить разность двух последовательностей:

var DaysHashSet1 = new HashSet<Days>() { Days.Friday, Days.Monday };
var DaysHashSet2 = new HashSet<Days>() { 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:

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. Его результатом является новый набор, в котором имеются элементы, как из первой, так и из второй последовательности. Повторяющиеся элементы добавляются в результат только один раз:

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:

var result = soft.Concat(hard);

Те элементы, которые встречаются в обоих наборах, дублируются.

Удаление дубликатов

Для удаления дублей в наборе используется метод Distinct:

var result = soft.Concat(hard).Distinct();

Последовательное применение методов Concat и Distinct будет подобно действию метода Union.


Работа с датами и временем

Для работы с датами и временем в .NET предназначена структура DateTime. Она представляет дату и время от 00:00:00 1 января 0001 года до 23:59:59 31 декабря 9999 года.

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

DateTime date1 = new DateTime();
Console.WriteLine(date1); // 01.01.0001 0:00:00

То есть мы получим минимально возможное значение, которое также можно получить следующим образом:

Console.WriteLine(DateTime.MinValue);

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

DateTime date1 = new DateTime(2015, 7, 20); // год - месяц - день
Console.WriteLine(date1); // 20.07.2015 0:00:00

Установка времени:

DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // год - месяц - день - час - минута - секунда
Console.WriteLine(date1); // 20.07.2015 18:30:25

Если необходимо получить текущую время и дату, то можно использовать ряд свойств DateTime:

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 года:

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 часа:

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):

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() и другие методы добавления отрицательные значения:

// вычтем три часа
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

Кроме операций сложения и вычитания еще есть ряд методов форматирования дат:

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
ttAM или PM
yПредставляет год как число из одной или двух цифр. Если год имеет более двух цифр, то в результате отображаются только две младшие цифры
yyПредставляет год как число из одной или двух цифр. Если год имеет более двух цифр, то в результате отображаются только две младшие цифры. Если год имеет одну цифру, то он дополняется начальным нулем
yyyГод из трех цифр
yyyyГод из четырех цифр
yyyyyГод из пяти цифр. Если в году меньше пяти цифр, то он дополняется начальными нулями
zПредставляет смещение в часах относительно времени UTC
zzПредставляет смещение в часах относительно времени UTC. Если смещение представляет одну цифру, то она дополняется начальным нулем.

Предыдущая лекция Следующая лекция
Строки.СодержаниеОбщие сведения о подпрограммах.