cs_misc_types.md 32 KB

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

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

Перечисления (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
}

Operation op;
op = Operation.Add;
Console.WriteLine(op); // Add
    
Console.ReadLine();

В программе мы можем присвоить значение этой переменной. При этом в качестве её значения должна выступать одна из констант, определенных в данном перечислении. То есть несмотря на то, что каждая константа сопоставляется с определенным числом, мы не можем присвоить ей числовое значение, например, Operation op = 1;. И также если мы будем выводить на консоль значение этой переменной, то мы получим имя константы, а не числовое значение. Если же необходимо получить числовое значение, то следует выполнить приведение к числовому типу:

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

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

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)

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

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

var tuple = (5, 10);

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

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.

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

var (name, age) = ("Tom", 23);
Console.WriteLine(name);    // Tom
Console.WriteLine(age);     // 23
Console.Read();

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

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

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

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

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

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

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

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<T> и SortedSet<T>. Оба они реализуют интерфейс ISet<T>. Класс HashSet<T> содержит неупорядоченный список различающихся элементов, а в SortedSet<T> элементы упорядочены.

Интерфейс ISet<T> предоставляет методы для создания, объединения нескольких множеств, пересечения множеств и определения, является ли одно множество надмножеством или подмножеством другого.

Ниже перечислены наиболее употребительные конструкторы, определенные в классе HashSet<T>:

  • public HashSet ()

    создается пустое множество

  • public HashSet(IEnumerable<T> collection)

    создается множество, состоящее из элементов указываемой коллекции collection

  • public HashSet(IEqualityCompare comparer)

    допускается указывать способ сравнения с помощью параметра comparer

  • public HashSet(IEnumerable<T> collection, IEqualityCompare comparer)

    создается множество, состоящее из элементов указываемой коллекции collection, и используется заданный способ сравнения comparer.

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

В этом классе предоставляется также метод RemoveWhere, удаляющий из множества элементы, удовлетворяющие заданному условию, или предикату. Помимо свойств, определенных в интерфейсах, которые реализуются в классе HashSet<T>, в него введено дополнительное свойство Comparer, приведенное ниже:

public IEqualityComparer<T> Comparer { get; }

Оно позволяет получать метод сравнения для вызывающего хеш-множества.

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

  • public SortedSet()

    создается пустое множество

  • public SortedSet(IEnumerable<T> collection)

    создается множество, состоящее из элементов указываемой коллекции collection

  • public SortedSet(IComparer comparer)

    допускается указывать способ сравнения с помощью параметра comparer

  • public SortedSet(IEnumerable<T> collection, IComparer comparer)

    создается множество, состоящее из элементов указываемой коллекции collection, и используется заданный способ сравнения comparer

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

В этом классе предоставляется также метод GetViewBetween, возвращающий часть множества в форме объекта типа SortedSet<T>, метод RemoveWhere, удаляющий из множества элементы, не удовлетворяющие заданному условию, или предикату, а также метод Reverse, возвращающий объект типа IEnumerable<T>, который циклически проходит множество в обратном порядке.

Помимо свойств, определенных в интерфейсах, которые реализуются в классе SortedSet<T>, в него введены дополнительные свойства, приведенные ниже:

  • public IComparer<T> Comparer { get; }
  • public T Max { get; }
  • public T Min { get; }

Свойство Comparer получает способ сравнения для вызывающего множества. Свойство Мах получает наибольшее значение во множестве, а свойство Min — наименьшее значение во множестве. Давайте рассмотрим пример использования множеств:

// Создадим два множества
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");
}

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

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

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

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

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

  1. Перечисления
  2. Кортежи
  3. Множества
  4. Дата и время
Предыдущая лекция   Следующая лекция
Строки. Содержание Общие сведения о подпрограммах.