Инструкции, операторы, циклы, условия, исключения в C#

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

Здесь рассматриваются следующие темы:

  • Операции с переменными
  • Понимание операторов выбора
  • Понимание операторов итерации
  • Приведение и преобразование между типами
  • Обработка исключений
  • Проверка переполнения
Содержание

Операции с переменными

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

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

var resultOfOperation = firstOperand operator secondOperand;

Некоторые операторы являются унарными, то есть они работают с одним операндом и могут применяться до или после операнда, как показано в следующем псевдокоде:

var resultOfOperation = onlyOperand operator;
var resultOfOperation2 = operator onlyOperand;

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

int x = 5;
int incrementedByOne = x++; // Постфиксная форма: сначала используется значение x, затем увеличивается
int incrementedByOneAgain = ++x; // Префиксная форма: сначала увеличивается, затем используется значение x
Type theTypeOfAnInteger = typeof(int); // Получение типа переменной
int howManyBytesInAnInteger = sizeof(int); // Получение размера типа в байтах

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

var resultOfOperation = firstOperand firstOperator secondOperand secondOperator thirdOperand;

В C# тернарный оператор представлен оператором условного выражения ?:, который используется для выбора одного из двух значений в зависимости от логического условия:

int a = 10, b = 20;
int result = a > b ? a : b; // Если a больше b, результат a, иначе результат b
Console.WriteLine(result); // Вывод: 20

Унарные операторы

Два распространенных унарных оператора используются для инкрементации (++) и декрементации (--) числа. Давайте напишем пример кода, чтобы показать, как они работают:

  1. Если вы завершили предыдущие главы, у вас уже должна быть папка Code в вашей пользовательской папке. Если нет, создайте её.
  2. В папке Code создайте папку с именем Chapter03.
  3. Запустите Visual Studio Code и закройте все открытые рабочие пространства или папки.
  4. Сохраните текущее рабочее пространство в папке Chapter03 как Chapter03.code-workspace.
  5. Создайте новую папку с именем Operators и добавьте её в рабочее пространство Chapter03.
  6. Перейдите в раздел Terminal | New Terminal.
  7. В терминале введите команду для создания нового консольного приложения в папке Operators.
  8. Откройте Program.cs.
  9. Статически импортируйте System.Console.
  10. В методе Main объявите две целочисленные переменные с именами a и b, установите a равной трём, инкрементируйте a, присвоив результат b, а затем выведите их значения, как показано в следующем коде:
    using static System.Console;
    
    class Program
    {
        static void Main()
        {
            int a = 3;
            int b = a++;
            WriteLine($"a is {a}, b is {b}");
        }
    }
    

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

    a is 4, b is 3
    

    Переменная b имеет значение 3, потому что оператор ++ выполняется после присваивания; это называется постфиксным оператором. Если вам нужно инкрементировать перед присваиванием, используйте префиксный оператор.

  11. Скопируйте и вставьте операторы, затем измените их, чтобы переименовать переменные и использовать префиксный оператор, как показано в следующем коде:
    using static System.Console;
    
    class Program
    {
        static void Main()
        {
            int a = 3;
            int b = a++;
            WriteLine($"a is {a}, b is {b}");
            
            int c = 3;
            int d = ++c; // инкрементируем c перед присваиванием
            WriteLine($"c is {c}, d is {d}");
        }
    }
    
  12. Перезапустите консольное приложение и обратите внимание на результат, как показано в следующем выводе:
    a is 4, b is 3
    c is 4, d is 4
    

Хорошая практика. Из-за путаницы между префиксными и постфиксными операторами для операторов инкрементации и декрементации при их комбинировании с оператором присваивания (=), дизайнеры языка программирования Swift решили отказаться от поддержки этого оператора в версии 3. Моя рекомендация по использованию в C# — никогда не комбинировать использование операторов ++ и -- с оператором присваивания. Выполняйте операции в отдельных операторах.

Пример разделения операций:

using static System.Console;

class Program
{
    static void Main()
    {
        int a = 3;
        a++;
        int b = a;
        WriteLine($"a is {a}, b is {b}");
        
        int c = 3;
        ++c;
        int d = c;
        WriteLine($"c is {c}, d is {d}");
    }
}

Бинарные арифметические операторы

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

  1. Добавьте следующие операторы в конец метода Main, чтобы объявить и присвоить значения двум целочисленным переменным e и f, а затем примените пять основных бинарных арифметических операторов к этим двум числам, как показано в следующем коде:
    using static System.Console;
    
    class Program
    {
        static void Main()
        {
            int e = 11;
            int f = 3;
            WriteLine($"e is {e}, f is {f}");
            WriteLine($"e + f = {e + f}");
            WriteLine($"e - f = {e - f}");
            WriteLine($"e * f = {e * f}");
            WriteLine($"e / f = {e / f}");
            WriteLine($"e % f = {e % f}");
        }
    }
    

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

    e is 11, f is 3
    e + f = 14
    e - f = 8
    e * f = 33
    e / f = 3
    e % f = 2
    

    Чтобы понять операторы деления / и модуля %, применяемые к целым числам, вспомните начальную школу. Представьте, что у вас есть одиннадцать конфет и три друга. Как вы можете разделить конфеты между своими друзьями? Вы можете дать по три конфеты каждому из друзей, и у вас останется две. Эти две конфеты — это модуль, также известный как остаток после деления. Если у вас двенадцать конфет, то каждый друг получает по четыре конфеты, и ничего не остается, поэтому остаток будет 0.

  2. Добавьте операторы для объявления и присвоения значения переменной типа double с именем g, чтобы показать разницу между целочисленным и вещественным делением, как показано в следующем коде:
    using static System.Console;
    
    class Program
    {
        static void Main()
        {
            int e = 11;
            int f = 3;
            WriteLine($"e is {e}, f is {f}");
            WriteLine($"e + f = {e + f}");
            WriteLine($"e - f = {e - f}");
            WriteLine($"e * f = {e * f}");
            WriteLine($"e / f = {e / f}");
            WriteLine($"e % f = {e % f}");
            
            double g = 11.0;
            WriteLine($"g is {g:N1}, f is {f}");
            WriteLine($"g / f = {g / f}");
        }
    }
    

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

    e is 11, f is 3
    e + f = 14
    e - f = 8
    e * f = 33
    e / f = 3
    e % f = 2
    g is 11.0, f is 3
    g / f = 3.6666666666666665
    

    Если первый операнд является числом с плавающей запятой, как g со значением 11.0, то оператор деления возвращает значение с плавающей запятой, например, 3.6666666666666665, а не целое число.

Операторы присваивания

Вы уже использовали самый распространенный оператор присваивания, =. Чтобы сделать ваш код более лаконичным, вы можете комбинировать оператор присваивания с другими операторами, такими как арифметические операторы, как показано в следующем коде:

int p = 6;
p += 3; // эквивалентно p = p + 3;
p -= 3; // эквивалентно p = p - 3;
p *= 3; // эквивалентно p = p * 3;
p /= 3; // эквивалентно p = p / 3;

Логические операторы

Логические операторы работают с булевыми значениями и возвращают либо true, либо false. Давайте рассмотрим бинарные логические операторы, которые работают с двумя булевыми значениями.

Пример с логическими операторами

Создайте новую папку и консольное приложение с именем BooleanOperators и добавьте его в рабочее пространство Chapter03. Не забудьте использовать Командную палитру, чтобы выбрать BooleanOperators как активный проект.

Код для логических операторов

В файле Program.cs, в методе Main, добавьте операторы для объявления двух булевых переменных со значениями true и false, а затем выведите таблицы истинности, показывающие результаты применения логических операторов И (AND), ИЛИ (OR) и исключающее ИЛИ (XOR), как показано в следующем коде:

using static System.Console;

class Program
{
    static void Main()
    {
        bool a = true;
        bool b = false;

        WriteLine($"AND | a | b ");
        WriteLine($"a   | {a & a,-5} | {a & b,-5} ");
        WriteLine($"b   | {b & a,-5} | {b & b,-5} ");
        WriteLine();

        WriteLine($"OR  | a | b ");
        WriteLine($"a   | {a | a,-5} | {a | b,-5} ");
        WriteLine($"b   | {b | a,-5} | {b | b,-5} ");
        WriteLine();

        WriteLine($"XOR | a | b ");
        WriteLine($"a   | {a ^ a,-5} | {a ^ b,-5} ");
        WriteLine($"b   | {b ^ a,-5} | {b ^ b,-5} ");
    }
}

Результаты выполнения программы

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

AND | a | b 
a   | True  | False 
b   | False | False 

OR  | a | b 
a   | True  | True  
b   | True  | False 

XOR | a | b 
a   | False | True  
b   | True  | False 

Для логического оператора И (&), оба операнда должны быть true, чтобы результат был true. Для логического оператора ИЛИ (|), любой из операндов может быть true, чтобы результат был true. Для логического оператора исключающее ИЛИ (^), любой из операндов может быть true (но не оба!), чтобы результат был true.

Дополнительная информация

Прочитайте о таблицах истинности по следующей ссылке: Truth Table.

Условные логические операторы

Условные логические операторы похожи на логические операторы, но используют два символа вместо одного, например, && вместо &, или || вместо |. Эти операторы также известны как логические операторы с коротким замыканием (short-circuiting Boolean operators).

Для объяснения условных логических операторов нужно немного рассказать о функциях. Функция выполняет операторы и затем возвращает значение. Это значение может быть булевым, например, true, и может использоваться в логической операции.

Пример использования условных логических операторов

  1. Создайте консольное приложение и добавьте код для использования условных логических операторов:
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            bool a = true;
            bool b = false;
    
            WriteLine($"a & DoStuff() = {a & DoStuff()}");
            WriteLine($"b & DoStuff() = {b & DoStuff()}");
    
            WriteLine($"a && DoStuff() = {a && DoStuff()}");
            WriteLine($"b && DoStuff() = {b && DoStuff()}");
        }
    
        private static bool DoStuff()
        {
            WriteLine("I am doing some stuff.");
            return true;
        }
    }
    
  2. В этом коде сначала объявляются две булевые переменные a и b, инициализируемые значениями true и false соответственно. Затем выполняются логические операции & и && с использованием функции DoStuff.
  3. Функция DoStuff выводит сообщение в консоль и возвращает значение true.

Результаты выполнения программы

Запустите консольное приложение и обратите внимание на результаты:

I am doing some stuff.
a & DoStuff() = True
I am doing some stuff.
b & DoStuff() = False

I am doing some stuff.
a && DoStuff() = True
b && DoStuff() = False
  1. Как видно из вывода, функция DoStuff была вызвана дважды для операций с &, но только один раз для операций с &&. Это связано с тем, что при использовании &&, если первый операнд равен false, второй операнд не вычисляется, так как результат уже известен — false.

Рекомендации по использованию

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

Дополнительная информация

Прочитайте о побочных эффектах по следующей ссылке: Side Effects in Computer Science.

Операторы побитового и бинарного сдвига

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

Пример использования побитовых и бинарных сдвигов:

  1. Создайте новую папку и консольное приложение с именем BitwiseAndShiftOperators и добавьте его в рабочую область.
  2. Добавьте в метод Main следующие операторы, чтобы объявить две целочисленные переменные со значениями 10 и 6, а затем выведите результаты применения побитовых операторов AND, OR и XOR:
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10; // 0000 1010
            int b = 6;  // 0000 0110
            WriteLine($"a = {a}");
            WriteLine($"b = {b}");
            WriteLine($"a & b = {a & b}"); // 2-битовая колонка
            WriteLine($"a | b = {a | b}"); // 8, 4 и 2-битовые колонки
            WriteLine($"a ^ b = {a ^ b}"); // 8 и 4-битовые колонки
        }
    }
    
  3. Запустите консольное приложение и обратите внимание на результаты:
    a = 10
    b = 6
    a & b = 2
    a | b = 14
    a ^ b = 12
    
  4. Добавьте в метод Main операторы для вывода результатов применения оператора сдвига влево к переменной a на три столбца, умножения a на 8 и сдвига вправо битов переменной b на один столбец:
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10; // 0000 1010
            int b = 6;  // 0000 0110
    
            WriteLine($"a = {a}");
            WriteLine($"b = {b}");
            WriteLine($"a & b = {a & b}"); // 2-битовая колонка
            WriteLine($"a | b = {a | b}"); // 8, 4 и 2-битовые колонки
            WriteLine($"a ^ b = {a ^ b}"); // 8 и 4-битовые колонки
    
            // Сдвиг влево на три битовые колонки
            WriteLine($"a << 3 = {a << 3}");
            // Умножение на 8
            WriteLine($"a * 8 = {a * 8}");
            // Сдвиг вправо на одну битовую колонку
            WriteLine($"b >> 1 = {b >> 1}");
        }
    }
    
  5. Запустите консольное приложение и обратите внимание на результаты:
    a = 10
    b = 6
    a & b = 2
    a | b = 14
    a ^ b = 12
    a << 3 = 80
    a * 8 = 80
    b >> 1 = 3
    

Объяснение результатов:

  • Побитовый AND (&): Возвращает 2, так как общая 1-битовая колонка для a и b — это 2-битовая колонка.
  • Побитовый OR (|): Возвращает 14, так как все биты a и b объединяются.
  • Побитовый XOR (^): Возвращает 12, так как исключительное OR возвращает 1, если биты различаются.
  • Сдвиг влево (<<): Возвращает 80, так как сдвиг влево на три бита эквивалентен умножению на 8.
  • Умножение на 8: Возвращает 80, подтверждая эквивалентность сдвига влево.
  • Сдвиг вправо (>>): Возвращает 3, так как сдвиг вправо на один бит делит число на 2.

Операторы ветвления (условные операторы)

Каждое приложение должно уметь выбирать из нескольких вариантов и переходить по различным путям кода. В C# существуют два оператора ветвления: if и switch. Вы можете использовать if для всех ваших нужд, но switch может упростить ваш код в некоторых общих сценариях, таких как обработка одного переменного значения, которое может иметь несколько значений, каждое из которых требует разной обработки.

Ветвление с оператором if

Оператор if определяет, какой путь следовать, оценивая логическое выражение. Если выражение истинно, то выполняется блок кода. Блок else является опциональным и выполняется, если выражение if ложно. Оператор if может быть вложенным.

Оператор if можно комбинировать с другими операторами if как ветви else if, как показано в следующем примере:

if (expression1)
{
    // выполняется, если expression1 истинно
}
else if (expression2)
{
    // выполняется, если expression1 ложно и expression2 истинно
}
else if (expression3)
{
    // выполняется, если expression1 и expression2 ложны,
    // а expression3 истинно
}
else
{
    // выполняется, если все выражения ложны
}

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

Создание консольного приложения для изучения операторов ветвления

  1. Создайте папку и консольное приложение с именем SelectionStatements и добавьте его в рабочую область.
  2. Добавьте следующие операторы внутрь метода Main для проверки наличия аргументов, переданных этому консольному приложению:
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                WriteLine("Аргументы отсутствуют.");
            }
            else
            {
                WriteLine("Присутствует как минимум один аргумент.");
            }
        }
    }
    
  3. Запустите консольное приложение, введя следующую команду в терминале:
    dotnet run
    

Если вы запустите приложение без аргументов, вы увидите сообщение «Аргументы отсутствуют». Если вы добавите хотя бы один аргумент, например, dotnet run arg1, вы увидите сообщение «Присутствует как минимум один аргумент».

Почему всегда следует использовать фигурные скобки с операторами if

Хотя можно написать код без фигурных скобок, как показано ниже:

if (args.Length == 0)
    WriteLine("There are no arguments.");
else
    WriteLine("There is at least one argument.");

того стиля следует избегать, так как это может привести к серьезным ошибкам. Например, знаменитая ошибка #gotofail в операционной системе iPhone iOS Apple. В течение 18 месяцев после выпуска iOS 6 в сентябре 2012 года в коде шифрования SSL была ошибка, из-за которой пользователи, пытающиеся подключиться к защищенным веб-сайтам через браузер Safari, были подвержены уязвимостям, так как важная проверка была случайно пропущена.

Дополнительная информация

Вы можете прочитать об этой ошибке по следующей ссылке: gotofail.com.

Несмотря на возможность не использовать фигурные скобки, ваш код не становится «более эффективным» без них; напротив, он становится менее поддерживаемым и потенциально более опасным.

Сопоставление с образцом (pattern matching) с оператором if

Функция, введенная в C# 7.0 и более поздних версиях, позволяет использовать сопоставление с образцом. Оператор if может использовать ключевое слово is в сочетании с объявлением локальной переменной, чтобы сделать ваш код безопаснее.

  1. Добавьте операторы в конец метода Main, чтобы если значение, хранящееся в переменной o, является int, то оно присваивалось локальной переменной i, которую можно использовать внутри оператора if. Это безопаснее, чем использование переменной o, так как мы точно знаем, что i является переменной типа int, а не чем-то другим:
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            // добавьте и уберите "" для изменения поведения
            object o = "3";
            int j = 4;
    
            if (o is int i)
            {
                WriteLine($"{i} x {j} = {i * j}");
            }
            else
            {
                WriteLine("o is not an int so it cannot multiply!");
            }
        }
    }
    
  2. Запустите консольное приложение и посмотрите результаты:
    o is not an int so it cannot multiply!
    
  3. Удалите двойные кавычки вокруг значения "3", чтобы значение, хранящееся в переменной o, было типа int, а не типа string.
    object o = 3;
    
  4. Перезапустите консольное приложение, чтобы увидеть результаты:
    3 x 4 = 12
    

Разветвление с оператором switch

Оператор switch отличается от оператора if, потому что он сравнивает одно выражение с набором возможных значений (case). Каждый раздел case должен завершаться одним из следующих способов:

  • Ключевым словом break
  • Ключевыми словами goto case
  • Без операторов
  • Ключевым словом return

Написание кода с оператором switch:

  1. Вставьте операторы для оператора switch после операторов if, написанных ранее. Первая строка является меткой, к которой можно перейти, вторая строка генерирует случайное число. Разветвления оператора switch основаны на значении этого случайного числа.
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            // Проверка наличия аргументов
            if (args.Length == 0)
            {
                WriteLine("There are no arguments.");
            }
            else
            {
                WriteLine("There is at least one argument.");
            }
    
            // Пример использования pattern matching
            // добавьте и уберите "" для изменения поведения
            object o = "3";
            int j = 4;
    
            if (o is int i)
            {
                WriteLine($"{i} x {j} = {i * j}");
            }
            else
            {
                WriteLine("o is not an int so it cannot multiply!");
            }
    
            // Измените значение o, чтобы проверить другой результат
            o = 3;
    
            if (o is int k)
            {
                WriteLine($"{k} x {j} = {k * j}");
            }
            else
            {
                WriteLine("o is not an int so it cannot multiply!");
            }
    
            // Пример использования оператора switch
            A_label:
            var number = (new Random()).Next(1, 7);
            WriteLine($"My random number is {number}");
    
            switch (number)
            {
                case 1:
                    WriteLine("One");
                    break; // переход к концу оператора switch
                case 2:
                    WriteLine("Two");
                    goto case 1;
                case 3:
                case 4:
                    WriteLine("Three or four");
                    goto case 1;
                case 5:
                    // пауза на полсекунды
                    System.Threading.Thread.Sleep(500);
                    goto A_label;
                default:
                    WriteLine("Default");
                    break;
            } // конец оператора switch
        }
    }
    
  2. Запуск консольного приложения. Запустите консольное приложение несколько раз, чтобы увидеть, что происходит в различных случаях случайных чисел. Пример вывода:
    My random number is 4
    Three or four
    One
    
    My random number is 2
    Two
    One
    
    My random number is 1
    One
    

Полезные ссылки

Вы можете узнать больше о ключевом слове goto и примерах его использования по следующей ссылке: goto keyword.

Использование pattern matching с оператором switch

Оператор switch поддерживает pattern matching, начиная с C# 7.0. Значения case могут быть не только литералами, но и шаблонами.

Пример использования pattern matching с оператором switch

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

  1. Добавьте следующую строку в начало файла, чтобы импортировать типы для работы с вводом/выводом:
    using System.IO;
    
  2. Добавьте следующие операторы в конец метода Main, чтобы объявить строку path к файлу, открыть его как поток только для чтения или для записи, и затем показать сообщение, основанное на типе и возможностях потока:
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            // Проверка наличия аргументов
            if (args.Length == 0)
            {
                WriteLine("There are no arguments.");
            }
            else
            {
                WriteLine("There is at least one argument.");
            }
    
            // Пример использования pattern matching
            // добавьте и уберите "" для изменения поведения
            object o = "3";
            int j = 4;
    
            if (o is int i)
            {
                WriteLine($"{i} x {j} = {i * j}");
            }
            else
            {
                WriteLine("o is not an int so it cannot multiply!");
            }
    
            // Измените значение o, чтобы проверить другой результат
            o = 3;
    
            if (o is int k)
            {
                WriteLine($"{k} x {j} = {k * j}");
            }
            else
            {
                WriteLine("o is not an int so it cannot multiply!");
            }
    
            // Пример использования оператора switch
            A_label:
            var number = (new Random()).Next(1, 7);
            WriteLine($"My random number is {number}");
    
            switch (number)
            {
                case 1:
                    WriteLine("One");
                    break; // переход к концу оператора switch
                case 2:
                    WriteLine("Two");
                    goto case 1;
                case 3:
                case 4:
                    WriteLine("Three or four");
                    goto case 1;
                case 5:
                    // пауза на полсекунды
                    System.Threading.Thread.Sleep(500);
                    goto A_label;
                default:
                    WriteLine("Default");
                    break;
            } // конец оператора switch
    
            // Pattern matching с оператором switch
            // string path = "/Users/markjprice/Code/Chapter03"; // для macOS
            string path = @"C:\Code\Chapter03"; // для Windows
    
            Write("Press R for readonly or W for write: ");
            ConsoleKeyInfo key = ReadKey();
            WriteLine();
            Stream s = null;
    
            if (key.Key == ConsoleKey.R)
            {
                s = File.Open(
                    Path.Combine(path, "file.txt"),
                    FileMode.OpenOrCreate,
                    FileAccess.Read);
            }
            else
            {
                s = File.Open(
                    Path.Combine(path, "file.txt"),
                    FileMode.OpenOrCreate,
                    FileAccess.Write);
            }
    
            string message = string.Empty;
    
            switch (s)
            {
                case FileStream writeableFile when s.CanWrite:
                    message = "The stream is a file that I can write to.";
                    break;
                case FileStream readOnlyFile:
                    message = "The stream is a read-only file.";
                    break;
                case MemoryStream ms:
                    message = "The stream is a memory address.";
                    break;
                default: // always evaluated last despite its current position
                    message = "The stream is some other type.";
                    break;
                case null:
                    message = "The stream is null.";
                    break;
            }
    
            WriteLine(message);
        }
    }
    
  3. Запуск консольного приложения. Запустите консольное приложение и отметьте, что переменная s объявлена как тип Stream, поэтому она может быть любым подтипом потока, таким как MemoryStream или FileStream. В этом коде поток создается с помощью метода File.Open, который возвращает FileStream, и в зависимости от нажатой клавиши он будет либо доступен для записи, либо только для чтения. Результатом будет сообщение, описывающее ситуацию.Пример вывода:
    Press R for readonly or W for write: R
    The stream is a read-only file.
    

Полезные ссылки. Вы можете узнать больше о pattern matching по следующей ссылке: Pattern matching.

Упрощение операторов switch с помощью switch выражений

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

Пример использования switch выражений

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

  1. Добавьте следующий код в конец метода Main, чтобы установить сообщение на основе типа и возможностей потока, используя switch выражение:
    message = s switch
    {
        FileStream writeableFile when s.CanWrite => "The stream is a file that I can write to.",
        FileStream readOnlyFile => "The stream is a read-only file.",
        MemoryStream ms => "The stream is a memory address.",
        null => "The stream is null.",
        _ => "The stream is some other type."
    };
    WriteLine(message);
    
  2. Полный код
    using System.IO;
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            // Проверка наличия аргументов
            if (args.Length == 0)
            {
                WriteLine("There are no arguments.");
            }
            else
            {
                WriteLine("There is at least one argument.");
            }
    
            // Пример использования pattern matching
            // добавьте и уберите "" для изменения поведения
            object o = "3";
            int j = 4;
    
            if (o is int i)
            {
                WriteLine($"{i} x {j} = {i * j}");
            }
            else
            {
                WriteLine("o is not an int so it cannot multiply!");
            }
    
            // Измените значение o, чтобы проверить другой результат
            o = 3;
    
            if (o is int k)
            {
                WriteLine($"{k} x {j} = {k * j}");
            }
            else
            {
                WriteLine("o is not an int so it cannot multiply!");
            }
    
            // Пример использования оператора switch
            A_label:
            var number = (new Random()).Next(1, 7);
            WriteLine($"My random number is {number}");
    
            switch (number)
            {
                case 1:
                    WriteLine("One");
                    break; // переход к концу оператора switch
                case 2:
                    WriteLine("Two");
                    goto case 1;
                case 3:
                case 4:
                    WriteLine("Three or four");
                    goto case 1;
                case 5:
                    // пауза на полсекунды
                    System.Threading.Thread.Sleep(500);
                    goto A_label;
                default:
                    WriteLine("Default");
                    break;
            } // конец оператора switch
    
            // Pattern matching с оператором switch
            // string path = "/Users/markjprice/Code/Chapter03"; // для macOS
            string path = @"C:\Code\Chapter03"; // для Windows
    
            Write("Press R for readonly or W for write: ");
            ConsoleKeyInfo key = ReadKey();
            WriteLine();
            Stream s = null;
    
            if (key.Key == ConsoleKey.R)
            {
                s = File.Open(
                    Path.Combine(path, "file.txt"),
                    FileMode.OpenOrCreate,
                    FileAccess.Read);
            }
            else
            {
                s = File.Open(
                    Path.Combine(path, "file.txt"),
                    FileMode.OpenOrCreate,
                    FileAccess.Write);
            }
    
            string message = string.Empty;
    
            switch (s)
            {
                case FileStream writeableFile when s.CanWrite:
                    message = "The stream is a file that I can write to.";
                    break;
                case FileStream readOnlyFile:
                    message = "The stream is a read-only file.";
                    break;
                case MemoryStream ms:
                    message = "The stream is a memory address.";
                    break;
                default: // always evaluated last despite its current position
                    message = "The stream is some other type.";
                    break;
                case null:
                    message = "The stream is null.";
                    break;
            }
    
            WriteLine(message);
    
            // Использование switch выражения
            message = s switch
            {
                FileStream writeableFile when s.CanWrite => "The stream is a file that I can write to.",
                FileStream readOnlyFile => "The stream is a read-only file.",
                MemoryStream ms => "The stream is a memory address.",
                null => "The stream is null.",
                _ => "The stream is some other type."
            };
    
            WriteLine(message);
        }
    }
    
  3. Запуск консольного приложения
    Запустите консольное приложение и убедитесь, что результат аналогичен тому, что был ранее. Использование switch выражений позволяет сделать код более кратким и читабельным, устраняя необходимость в явных ключевых словах case и break.Пример вывода:
    Press R for readonly or W for write: R
    The stream is a read-only file.
    The stream is a read-only file.
    

Полезные ссылки

  • Вы можете узнать больше о pattern matching по следующей ссылке: Pattern matching
  • Вы можете узнать больше о паттернах и switch выражениях по следующей ссылке: Do more with patterns in C# 8.0

Циклы в C#

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

Цикл с оператором while

Оператор while оценивает булево выражение и продолжает цикл, пока это выражение истинно.

Пример использования while

  1. Создайте новую папку и консольное приложение под названием IterationStatements и добавьте его в рабочее пространство.
  2. Введите следующий код внутри метода Main:
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            int x = 0;
            while (x < 10)
            {
                WriteLine(x);
                x++;
            }
        }
    }
    
  3. Запустите консольное приложение и посмотрите результаты, которые должны быть числами от 0 до 9:
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

Цикл с оператором do

Оператор do похож на while, за исключением того, что булево выражение проверяется внизу блока, а не вверху. Это означает, что блок выполняется как минимум один раз.

Пример использования do

  1. Введите следующий код в конец метода Main:
    string password = string.Empty;
    do
    {
        Write("Enter your password: ");
        password = ReadLine();
    }
    while (password != "Pa$$w0rd");
    WriteLine("Correct!");
    
  2. Запустите консольное приложение и обратите внимание, что вас будет неоднократно запрашивать на ввод пароля, пока вы не введете его правильно:
    Enter your password: password
    Enter your password: 12345678
    Enter your password: ninja
    Enter your password: correct horse battery staple
    Enter your password: Pa$$w0rd
    Correct!
    
  3. Как дополнительное задание, добавьте операторы, чтобы пользователь мог сделать только десять попыток перед выводом сообщения об ошибке.
    using static System.Console;
    
    class Program
    {
        static void Main(string[] args)
        {
            int x = 0;
            while (x < 10)
            {
                WriteLine(x);
                x++;
            }
    
            string password = string.Empty;
            int attempts = 0;
            const int maxAttempts = 10;
    
            do
            {
                Write("Enter your password: ");
                password = ReadLine();
                attempts++;
    
                if (attempts >= maxAttempts)
                {
                    WriteLine("Too many attempts! Access denied.");
                    return;
                }
            }
            while (password != "Pa$$w0rd");
    
            WriteLine("Correct!");
        }
    }
    

Цикл с оператором for

Оператор for аналогичен оператору while, но является более лаконичным. Он сочетает в себе:

  • Инициализирующее выражение, которое выполняется один раз в начале цикла.
  • Условное выражение, которое выполняется на каждой итерации в начале цикла для проверки, следует ли продолжать цикл.
  • Итерационное выражение, которое выполняется на каждой итерации в конце цикла.

Оператор for часто используется с целочисленным счетчиком. Рассмотрим пример.

Пример использования for

  1. Введите оператор for для вывода чисел от 1 до 10:
    for (int y = 1; y <= 10; y++)
    {
        WriteLine(y);
    }
    
  2. Запустите консольное приложение и посмотрите результат, который должен быть числами от 1 до 10.

Цикл с оператором foreach

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

Пример использования foreach

  1. Введите операторы для создания массива строковых переменных и вывода длины каждой из них:
    string[] names = { "Adam", "Barry", "Charlie" };
    foreach (string name in names)
    {
        WriteLine($"{name} has {name.Length} characters.");
    }
    
  2. Запустите консольное приложение и посмотрите результаты:
    Adam has 4 characters.
    Barry has 5 characters.
    Charlie has 7 characters.
    

Как работает foreach изнутри

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

  1. Тип должен иметь метод с именем GetEnumerator, который возвращает объект.
  2. Возвращаемый объект должен иметь свойство с именем Current и метод с именем MoveNext.
  3. Метод MoveNext должен возвращать true, если есть еще элементы для перебора, или false, если больше нет элементов.

Существуют интерфейсы IEnumerable и IEnumerable<T>, которые формально определяют эти правила, но технически компилятор не требует, чтобы типы реализовывали эти интерфейсы.

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

IEnumerator e = names.GetEnumerator();
while (e.MoveNext())
{
    string name = (string)e.Current; // Current доступен только для чтения!
    WriteLine($"{name} has {name.Length} characters.");
}

Из-за использования итератора переменная, объявленная в операторе foreach, не может использоваться для изменения значения текущего элемента.

Приведения и преобразования типов

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

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

Приведение (casting) бывает двух видов: неявное и явное. Неявное приведение происходит автоматически и безопасно, то есть вы не потеряете никакой информации. Явное приведение должно выполняться вручную, так как оно может потерять информацию, например, точность числа. При явном приведении вы сообщаете компилятору C#, что понимаете и принимаете риск.

Пример приведения чисел неявно и явно

Создание нового проекта

  1. Создайте новую папку и проект консольного приложения с именем CastingConverting и добавьте его в рабочее пространство.

Пример неявного приведения

  1. В методе Main введите операторы для объявления и присвоения целочисленной переменной и переменной с плавающей точкой, а затем неявно приведите значение целого числа при присвоении его переменной с плавающей точкой:
    int a = 10;
    double b = a; // целое число можно безопасно привести к числу с плавающей точкой
    WriteLine(b);
    
  2. В методе Main введите следующие операторы:
    double c = 9.8;
    int d = c; // компилятор выдаст ошибку для этой строки
    WriteLine(d);
    

Явное приведение

Вы не можете неявно привести переменную типа double к переменной типа int, так как это потенциально небезопасно и может привести к потере данных.

  1. Просмотрите окно ПРОБЛЕМ (Problems) и обратите внимание на сообщение об ошибке:
    error CS0266: Cannot implicitly convert type 'double' to 'int'. An explicit conversion exists (are you missing a cast?)
    
  2. В окне ТЕРМИНАЛ введите команду dotnet run и обратите внимание на сообщение об ошибке:
    The build failed. Fix the build errors and run again.
    

    Вы должны явно привести переменную типа double к переменной типа int с помощью пары круглых скобок вокруг типа, к которому вы хотите привести значение. Эта пара круглых скобок называется оператором приведения.

  3. Измените оператор присваивания для переменной d следующим образом:
    int d = (int)c;
    WriteLine(d); // d равно 9, теряя часть .8
    
  4. Запустите консольное приложение, чтобы увидеть результаты:
    10
    9
    

Приведение между большими и маленькими целыми числами

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

  1. Введите операторы для объявления и присвоения переменной long 64-битного целого числа переменной int 32-битного целого числа, сначала используя небольшое значение, которое будет работать, а затем слишком большое значение, которое не будет:
    long e = 10;
    int f = (int)e;
    WriteLine($"e is {e:N0} and f is {f:N0}");
    e = long.MaxValue;
    f = (int)e;
    WriteLine($"e is {e:N0} and f is {f:N0}");
    
  2. Запустите консольное приложение, чтобы увидеть результаты:
    e is 10 and f is 10
    e is 9,223,372,036,854,775,807 and f is -1
    
  3. Измените значение e на 5 миллиардов:
    e = 5_000_000_000;
    
  4. Запустите консольное приложение, чтобы увидеть результаты:
    e is 5,000,000,000 and f is 705,032,704
    

Преобразование с использованием типа System.Convert

Альтернативой использованию оператора приведения является использование типа System.Convert. Этот тип может преобразовывать данные из всех числовых типов C#, а также логические значения, строки и значения даты и времени.

Давайте напишем код, чтобы увидеть это в действии:

  1. В начале файла Program.cs статически импортируйте класс System.Convert, как показано в следующем коде:
    using static System.Convert;
    
  2. Добавьте операторы в конец метода Main, чтобы объявить и присвоить значение переменной типа double, преобразовать её в целое число и затем вывести оба значения в консоль:
    double g = 9.8;
    int h = ToInt32(g);
    WriteLine($"g is {g} and h is {h}");
    
  3. Запустите консольное приложение и просмотрите результат, который должен быть следующим:
    g is 9.8 and h is 10
    

Одним из различий между приведением и преобразованием является то, что преобразование округляет значение double 9.8 до 10 вместо обрезки части после десятичной точки.

Объяснение

  • Приведение: Когда вы используете оператор приведения (int), дробная часть числа просто отбрасывается, что приводит к потере точности.
  • Преобразование: Использование System.Convert.ToInt32 включает округление значения до ближайшего целого числа, что обеспечивает более корректное преобразование дробных чисел.

Примеры использования System.Convert

Кроме преобразования между числовыми типами, System.Convert можно использовать для преобразования других типов данных. Вот несколько примеров:

Преобразование строки в число

string numberString = "123";
int number = ToInt32(numberString);
WriteLine($"The number is {number}");

Преобразование строки в дату

string dateString = "2024-07-03";
DateTime date = ToDateTime(dateString);
WriteLine($"The date is {date:yyyy-MM-dd}");

Преобразование логического значения в строку

bool flag = true;
string flagString = ToString(flag);
WriteLine($"The flag is {flagString}");

Округление чисел

Вы уже видели, что оператор приведения обрезает десятичную часть числа, а методы System.Convert округляют вверх или вниз. Но каковы правила округления в C#?

Понимание правил округления по умолчанию

В начальной школе России учат округлять вверх, если десятичная часть равна .5 или выше, и округлять вниз, если десятичная часть меньше. Посмотрим, следует ли C# этому правилу.

  1. В конце метода Main добавьте операторы для объявления и присвоения массива значений типа double, преобразования каждого из них в целое число и вывода результата в консоль:
    foreach (double n in doubles)
    {
        WriteLine(format:
        "Math.Round({0}, 0, MidpointRounding.AwayFromZero) is {1}",
        arg0: n,
        arg1: Math.Round(value: n, digits: 0,
        mode: MidpointRounding.AwayFromZero));
    }
    
  2. Запустите консольное приложение и просмотрите результат:
    Math.Round(9.49, 0, MidpointRounding.AwayFromZero) is 9
    Math.Round(9.5, 0, MidpointRounding.AwayFromZero) is 10
    Math.Round(9.51, 0, MidpointRounding.AwayFromZero) is 10
    Math.Round(10.49, 0, MidpointRounding.AwayFromZero) is 10
    Math.Round(10.5, 0, MidpointRounding.AwayFromZero) is 11
    Math.Round(10.51, 0, MidpointRounding.AwayFromZero) is 11
    

Мы видим, что правило округления в C# немного отличается от правила начальной школы:

  • Всегда округляет вниз, если десятичная часть меньше середины (0.5).
  • Всегда округляет вверх, если десятичная часть больше середины (0.5).
  • Округляет вверх, если десятичная часть равна середине (0.5) и целая часть нечетная, но округляет вниз, если целая часть четная.

Это правило называется округлением банкира (Banker’s Rounding), и оно предпочтительнее, так как уменьшает смещение, чередуя округление вверх и вниз.

Контроль правил округления

Вы можете контролировать правила округления, используя метод Round класса Math.

  1. В конце метода Main добавьте операторы для округления каждого значения типа double по правилу округления «от нуля» (AwayFromZero) и выведите результат в консоль:
  2. Запустите консольное приложение и просмотрите результат:

Объяснение

  • Banker’s Rounding: По умолчанию используется правило округления банкира, которое округляет к ближайшему четному числу при значении 0.5.
  • AwayFromZero: Это правило, также известное как правило начальной школы, всегда округляет 0.5 вверх.

Примеры использования Math.Round

Округление до ближайшего целого числа

double value = 10.75;
int roundedValue = (int)Math.Round(value, MidpointRounding.AwayFromZero);
WriteLine($"Rounded value is {roundedValue}"); // Output: Rounded value is 11

Округление до определенного количества десятичных знаков

double value = 10.755;
double roundedValue = Math.Round(value, 2, MidpointRounding.AwayFromZero);
WriteLine($"Rounded value is {roundedValue}"); // Output: Rounded value is 10.76

Преобразование любого типа в строку

Наиболее часто используемое преобразование — это преобразование любого типа в строку для отображения в виде читаемого текста. Все типы имеют метод ToString, который они наследуют от класса System.Object.

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

Преобразование типов в строку

  1. В конце метода Main добавьте операторы для объявления нескольких переменных, преобразования их в строковое представление и вывода их в консоль:
    int number = 12;
    WriteLine(number.ToString());
    
    bool boolean = true;
    WriteLine(boolean.ToString());
    
    DateTime now = DateTime.Now;
    WriteLine(now.ToString());
    
    object me = new object();
    WriteLine(me.ToString());
    
  2. Запустите консольное приложение и просмотрите результат:
    12
    True
    27/01/2019 13:48:54
    System.Object
    

Объяснение

  • Число (int): Преобразуется в строку, представляющую его числовое значение.
  • Логическое значение (bool): Преобразуется в строку «True» или «False».
  • Дата и время (DateTime): Преобразуется в строку, представляющую текущую дату и время.
  • Объект (object): По умолчанию преобразуется в строку, содержащую имя его типа.

Форматирование строк

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

Пример: Форматирование чисел

double pi = 3.14159265359;
WriteLine(pi.ToString("F2")); // Output: 3.14
WriteLine(pi.ToString("N2")); // Output: 3.14 (with thousand separators)
WriteLine(pi.ToString("E"));  // Output: 3.141593E+00 (scientific notation)

Пример: Форматирование дат

DateTime now = DateTime.Now;
WriteLine(now.ToString("d"));  // Output: 27/01/2019 (short date)
WriteLine(now.ToString("D"));  // Output: Sunday, January 27, 2019 (long date)
WriteLine(now.ToString("t"));  // Output: 13:48 (short time)
WriteLine(now.ToString("T"));  // Output: 13:48:54 (long time)
WriteLine(now.ToString("f"));  // Output: Sunday, January 27, 2019 13:48 (full date short time)
WriteLine(now.ToString("F"));  // Output: Sunday, January 27, 2019 13:48:54 (full date long time)
WriteLine(now.ToString("M"));  // Output: January 27 (month day)
WriteLine(now.ToString("Y"));  // Output: January 2019 (year month)

Преобразование бинарного объекта в строку

Когда у вас есть бинарный объект, такой как изображение или видео, который нужно сохранить или передать, иногда нежелательно отправлять его сырые биты, так как вы не знаете, как эти биты могут быть неверно истолкованы, например, сетевым протоколом или другой операционной системой. Наиболее безопасным является преобразование бинарного объекта в строку безопасных символов с использованием кодирования Base64.

Тип Convert в C# предоставляет пару методов, ToBase64String и FromBase64String, которые выполняют это преобразование.

Пример использования методов ToBase64String и FromBase64String

  1. В конце метода Main добавьте следующие операторы для создания массива байтов, случайным образом заполненного значениями байтов, вывода каждого байта в удобном формате на консоль и затем вывода тех же байтов, преобразованных в Base64, на консоль:
    // allocate array of 128 bytes
    byte[] binaryObject = new byte[128];
    
    // populate array with random bytes
    (new Random()).NextBytes(binaryObject);
    
    WriteLine("Binary Object as bytes:");
    for (int index = 0; index < binaryObject.Length; index++)
    {
        Write($"{binaryObject[index]:X2} ");
    }
    WriteLine();
    
    // convert to Base64 string and output as text
    string encoded = Convert.ToBase64String(binaryObject);
    WriteLine($"Binary Object as Base64: {encoded}");
    

Пояснение к коду

  • Создание массива байтов: Сначала выделяется массив размером 128 байт.
  • Заполнение массива случайными значениями: С помощью класса Random массив заполняется случайными байтами.
  • Вывод байтов в консоль: Используется цикл for для форматированного вывода каждого байта в шестнадцатеричном формате (код :X2).
  • Преобразование в строку Base64: Массив байтов преобразуется в строку Base64 с помощью метода Convert.ToBase64String.
  • Вывод строки Base64 в консоль: Полученная строка выводится в консоль.

Пример вывода

Binary Object as bytes:
B3 4D 55 DE 2D E6 BB CF BE 4D E6 53 C3 C2 9B 67 03 45 F9 E5 20 61 7E 4F 7A 81 EC 49 F0 49 1D 8E D4 F7 DB 54 AF A0 81 05 B8 BE CE F8 36 90 7A D4 36 42 04 75 81 1B AB 51 CE 05 63 AC 22 72 DE 74 2F 57 7F CB E7 47 B7 62 C3 F4 2D 61 93 85 18 EA 06 17 12 AE 44 A8 0D B8 4C 89 85 A9 3C D5 E2 46 E0 59 C9 DF 10 AF ED EF 8A A1 B1 8D EE 4A BE 48 EC 79 A5 0A 5F 2F 30 87 4A C7 7F 5D C1 0D 26 EE

Binary Object as Base64: s01V3i0Ou8++TeZTw8KbZwNF+eUgYX5PeoHsSfBJHY7U99tUr6CBBbi+zvg2kHrUNkIEdYEbq1HOBWOsInLedC9Xf8vnR7diw/QtYZOFGOoGFxKuRKgNuEyJhak81eJG4FnJ3xCv7e+KobGN7kq+SOx5pQpfLzCHSsd/XcENJu4=

Объяснение

  • Шестнадцатеричный формат: Каждый байт выводится в шестнадцатеричном формате, например, B3 или 4D, что позволяет визуально оценить содержимое бинарного объекта.
  • Base64 формат: Бинарные данные преобразуются в строку Base64, что позволяет безопасно передавать и хранить бинарные данные в текстовом формате.

Разбор из строк в числа или даты и время

Преобразование строк в числа или значения даты и времени — это вторая по частоте операция преобразования. Метод Parse выполняет преобразование строки в число или значение даты и времени. Давайте посмотрим, как это работает на практике.

Пример использования метода Parse

  1. В методе Main добавьте операторы для разбора целого числа и значения даты и времени из строк и затем выведите результат на консоль:
    int age = int.Parse("27");
    DateTime birthday = DateTime.Parse("4 July 1980");
    WriteLine($"I was born {age} years ago.");
    WriteLine($"My birthday is {birthday}.");
    WriteLine($"My birthday is {birthday:D}.");
    
  2. Запустите консольное приложение и просмотрите результат:
    I was born 27 years ago.
    My birthday is 04/07/1980 00:00:00.
    My birthday is 04 July 1980.
    

Объяснение

  • Разбор целого числа: Строка "27" успешно преобразуется в целое число 27 с помощью метода int.Parse.
  • Разбор даты и времени: Строка "4 July 1980" преобразуется в значение DateTime с помощью метода DateTime.Parse. При выводе значения даты и времени по умолчанию используется короткий формат даты и времени. Использование форматного кода D выводит только часть даты в длинном формате.

Ошибки при использовании метода Parse

Метод Parse вызывает ошибку, если строка не может быть преобразована. Давайте рассмотрим пример:

  1. Добавьте оператор в конец метода Main для попытки преобразования строки, содержащей буквы, в целое число:
    int count = int.Parse("abc");
    
  2. Запустите консольное приложение и просмотрите результат:
    Unhandled Exception: System.FormatException: Input string was not in a correct format.
    

Обработка ошибок при использовании метода Parse

Чтобы избежать исключений при разборе строк, рекомендуется использовать методы TryParse, которые возвращают логическое значение, указывающее, успешно ли прошло преобразование:

if (int.TryParse("abc", out int count))
{
    WriteLine($"Parsed successfully: {count}");
}
else
{
    WriteLine("Failed to parse 'abc' as an integer.");
}

Пример использования TryParse

int age;
if (int.TryParse("27", out age))
{
    WriteLine($"Parsed age: {age}");
}
else
{
    WriteLine("Failed to parse age.");
}

DateTime birthday;
if (DateTime.TryParse("4 July 1980", out birthday))
{
    WriteLine($"Parsed birthday: {birthday:D}");
}
else
{
    WriteLine("Failed to parse birthday.");
}

Методы Parse и TryParse позволяют преобразовывать строки в числа и значения даты и времени. Использование метода TryParse предпочтительно, так как оно позволяет избежать исключений и обрабатывать ошибки преобразования более элегантно.

Использование русских региональных настроек при работе с датами

В работе с датами в C# часто требуется учитывать локальные стандарты форматирования, особенно если программа предназначена для пользователей из определенной страны. В России даты обычно отображаются в формате «дд.мм.гггг» (например, 03.07.2024).

Примеры работы с датами в российских стандартах

  1. Установка культуры для текущего потока

Для корректного форматирования дат согласно российским стандартам, можно установить русскую культуру для текущего потока:

using System;
using System.Globalization;
using System.Threading;

class Program
{
    static void Main()
    {
        // Установка культуры для текущего потока
        Thread.CurrentThread.CurrentCulture = new CultureInfo("ru-RU");
        Thread.CurrentThread.CurrentUICulture = new CultureInfo("ru-RU");

        // Пример работы с датой
        DateTime now = DateTime.Now;
        Console.WriteLine($"Текущая дата и время: {now}");
        Console.WriteLine($"Текущая дата: {now:dd.MM.yyyy}");
        Console.WriteLine($"Текущая дата (длинный формат): {now:D}");
    }
}
  1. Разбор строки с датой в российском формате

Для разбора строки с датой в российском формате можно использовать метод DateTime.ParseExact с указанием формата:

using System;
using System.Globalization;

class Program
{
    static void Main()
    {
        string dateString = "03.07.2024";
        DateTime parsedDate = DateTime.ParseExact(dateString, "dd.MM.yyyy", CultureInfo.InvariantCulture);
        Console.WriteLine($"Parsed date: {parsedDate}");
    }
}
  1. Форматирование даты в строку

Для форматирования даты в строку в российском формате можно использовать метод ToString с указанием формата:

using System;

class Program
{
    static void Main()
    {
        DateTime now = DateTime.Now;
        string formattedDate = now.ToString("dd.MM.yyyy");
        Console.WriteLine($"Formatted date: {formattedDate}");
    }
}
  1. Использование DateTime.TryParseExact для безопасного разбора строки с датой

Метод DateTime.TryParseExact позволяет безопасно разбирать строку с датой, избегая исключений:

using System;
using System.Globalization;

class Program
{
    static void Main()
    {
        string dateString = "03.07.2024";
        if (DateTime.TryParseExact(dateString, "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedDate))
        {
            Console.WriteLine($"Parsed date: {parsedDate}");
        }
        else
        {
            Console.WriteLine("Failed to parse date.");
        }
    }
}

Обработка исключений при преобразовании типов

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

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

Хорошая практика: избегайте написания кода, который может вызвать исключение, когда это возможно, например, с помощью проверок if, но иногда это неизбежно. В таких случаях вы можете поймать исключение и обработать его более подходящим образом, чем это делает стандартное поведение.

Обертывание кода, подверженного ошибкам, в блок try

Когда вы знаете, что определенное выражение может вызвать ошибку, следует обернуть его в блок try. Например, попытка преобразования текста в число может вызвать ошибку. Любые выражения в блоке catch будут выполнены только в случае, если в блоке try будет выброшено исключение.

Не обязательно что-то делать внутри блока catch. Давайте посмотрим, как это работает на практике:

  1. Создайте папку и консольное приложение с именем HandlingExceptions и добавьте его в рабочую область.
  2. В методе Main добавьте выражения, чтобы попросить пользователя ввести свой возраст, а затем выведите его возраст в консоль, как показано в следующем коде:
    using System;
    
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Before parsing");
            Console.Write("What is your age? ");
            string input = Console.ReadLine();
            
            try
            {
                int age = int.Parse(input);
                Console.WriteLine($"You are {age} years old.");
            }
            catch
            {
            }
    
            Console.WriteLine("After parsing");
        }
    }
    

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

  3. Запустите консольное приложение.
  4. Введите корректный возраст, например, 47, и посмотрите результат:
    Before parsing
    What is your age? 47
    You are 47 years old.
    After parsing
    
  5. Запустите консольное приложение снова.
  6. Введите некорректный возраст, например, «kermit», и посмотрите результат:
    Before parsing
    What is your age? kermit
    After parsing
    

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

Обработка исключений с выводом сообщения об ошибке

Чтобы улучшить обработку ошибок, можно вывести сообщение об ошибке в блоке catch. Вот как это сделать:

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("Before parsing");
        Console.Write("What is your age? ");
        string input = Console.ReadLine();
        
        try
        {
            int age = int.Parse(input);
            Console.WriteLine($"You are {age} years old.");
        }
        catch (FormatException)
        {
            Console.WriteLine("The input is not a valid number.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An unexpected error occurred: {ex.Message}");
        }

        Console.WriteLine("After parsing");
    }
}

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

Before parsing
What is your age? kermit
The input is not a valid number.
After parsing

Обработка всех исключений

Чтобы получить информацию о любом типе исключения, которое может произойти, вы можете объявить переменную типа System.Exception в блоке catch:

  1. Добавьте объявление переменной исключения в блок catch и используйте её для вывода информации об исключении в консоль, как показано в следующем коде:
    catch(Exception ex)
    {
        Console.WriteLine($"{ex.GetType()} says {ex.Message}");
    }
    
  2. Запустите консольное приложение.
  3. Введите некорректный возраст, например, «kermit», и посмотрите результат:
    Before parsing
    What is your age? kermit
    System.FormatException says Input string was not in a correct format.
    After parsing
    

Обработка конкретных исключений

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

  1. Оставьте существующий блок catch, и выше него добавьте новый блок catch для типа исключения FormatException, как показано в следующем коде:
    catch (FormatException)
    {
        Console.WriteLine("The age you entered is not a valid number format.");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"{ex.GetType()} says {ex.Message}");
    }
    
  2. Запустите консольное приложение.
  3. Введите некорректный возраст, например, «kermit», и посмотрите результат:
    Before parsing
    What is your age? kermit
    The age you entered is not a valid number format.
    After parsing
    

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

  4. Запустите консольное приложение снова.
  5. Введите число, которое слишком велико для типа int, например, «9876543210», и посмотрите результат:
    Before parsing
    What is your age? 9876543210
    System.OverflowException says Value was either too large or too small for an Int32.
    After parsing
    

Добавление блока для конкретного исключения

Добавим еще один блок catch для типа исключения OverflowException:

catch (OverflowException)
{
    Console.WriteLine("Your age is a valid number format but it is either too big or too small.");
}
catch (FormatException)
{
    Console.WriteLine("The age you entered is not a valid number format.");
}
catch (Exception ex)
{
    Console.WriteLine($"{ex.GetType()} says {ex.Message}");
}
  1. Запустите консольное приложение.
  2. Введите слишком большое число, и посмотрите результат:
    Before parsing
    What is your age? 9876543210
    Your age is a valid number format but it is either too big or too small.
    After parsing
    

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

Понравилась статья? Поделиться с друзьями:
Школа Виктора Комлева
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.