Эта статья посвящена написанию кода, который выполняет простые операции с переменными, принимает решения, выполняет сопоставление с образцом, повторяет блоки операторов, преобразует значения переменных или выражений из одного типа в другой, обрабатывает исключения и проверяет переполнения в числовых переменных.
Здесь рассматриваются следующие темы:
- Операции с переменными
- Понимание операторов выбора
- Понимание операторов итерации
- Приведение и преобразование между типами
- Обработка исключений
- Проверка переполнения
Операции с переменными
Операторы выполняют простые операции, такие как сложение и умножение, с операндами, такими как переменные и литеральные значения. Они обычно возвращают новое значение, которое является результатом операции и может быть присвоено переменной.
Большинство операторов являются бинарными, что означает, что они работают с двумя операндами, как показано в следующем псевдокоде:
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
Унарные операторы
Два распространенных унарных оператора используются для инкрементации (++
) и декрементации (--
) числа. Давайте напишем пример кода, чтобы показать, как они работают:
- Если вы завершили предыдущие главы, у вас уже должна быть папка
Code
в вашей пользовательской папке. Если нет, создайте её. - В папке
Code
создайте папку с именемChapter03
. - Запустите Visual Studio Code и закройте все открытые рабочие пространства или папки.
- Сохраните текущее рабочее пространство в папке
Chapter03
какChapter03.code-workspace
. - Создайте новую папку с именем
Operators
и добавьте её в рабочее пространствоChapter03
. - Перейдите в раздел
Terminal | New Terminal
. - В терминале введите команду для создания нового консольного приложения в папке
Operators
. - Откройте
Program.cs
. - Статически импортируйте
System.Console
. - В методе
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, потому что оператор++
выполняется после присваивания; это называется постфиксным оператором. Если вам нужно инкрементировать перед присваиванием, используйте префиксный оператор. - Скопируйте и вставьте операторы, затем измените их, чтобы переименовать переменные и использовать префиксный оператор, как показано в следующем коде:
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}"); } }
- Перезапустите консольное приложение и обратите внимание на результат, как показано в следующем выводе:
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}");
}
}
Бинарные арифметические операторы
Инкремент и декремент являются унарными арифметическими операторами. Другие арифметические операторы обычно бинарные и позволяют выполнять арифметические операции над двумя числами. Давайте рассмотрим, как это работает:
- Добавьте следующие операторы в конец метода
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. - Добавьте операторы для объявления и присвоения значения переменной типа
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
, и может использоваться в логической операции.
Пример использования условных логических операторов
- Создайте консольное приложение и добавьте код для использования условных логических операторов:
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; } }
- В этом коде сначала объявляются две булевые переменные
a
иb
, инициализируемые значениямиtrue
иfalse
соответственно. Затем выполняются логические операции&
и&&
с использованием функцииDoStuff
. - Функция
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
- Как видно из вывода, функция
DoStuff
была вызвана дважды для операций с&
, но только один раз для операций с&&
. Это связано с тем, что при использовании&&
, если первый операнд равенfalse
, второй операнд не вычисляется, так как результат уже известен —false
.
Рекомендации по использованию
Теперь понятно, почему условные логические операторы называются операторами с коротким замыканием. Они могут сделать ваши приложения более эффективными, но также могут ввести тонкие ошибки в случаях, когда вы предполагаете, что функция всегда будет вызвана. Лучше избегать их использования в комбинации с функциями, которые вызывают побочные эффекты.
Дополнительная информация
Прочитайте о побочных эффектах по следующей ссылке: Side Effects in Computer Science.
Логические операторы используются для выполнения логических операций с булевыми значениями (true и false). Основные логические операторы в C#:
- AND (&&): Возвращает true, если оба операнда истинны.
bool result = (a > 0 && b < 10); // true, если a больше 0 И b меньше 10
- OR (||): Возвращает true, если хотя бы один из операндов истинен.
bool result = (a < 0 || b > 10); // true, если a меньше 0 ИЛИ b больше 10
- NOT (!): Инвертирует логическое значение. Если значение true, то становится false, и наоборот.
bool result = !(a > 0); // true, если a не больше 0
Операторы сравнения
Операторы сравнения
Операторы сравнения используются для сравнения значений и возвращают булевы результаты (true или false). Вот основные операторы сравнения в C#:
- Равно (==): Проверяет, равны ли два значения.
bool isEqual = (a == b); // true, если a равно b
- Не равно (!=): Проверяет, не равны ли два значения.
bool isNotEqual = (a != b); // true, если a не равно b
- Больше (> ): Проверяет, больше ли одно значение другого.
bool isGreater = (a > b); // true, если a больше b
- Меньше (<): Проверяет, меньше ли одно значение другого.
bool isLess = (a < b); // true, если a меньше b
- Больше или равно (>=): Проверяет, больше или равно ли одно значение другому.
bool isGreaterOrEqual = (a >= b); // true, если a больше или равно b
- Меньше или равно (<=): Проверяет, меньше или равно ли одно значение другому.
bool isLessOrEqual = (a <= b); // true, если a меньше или равно b
Операторы побитового и бинарного сдвига
Побитовые операторы влияют на биты в числе, а операторы бинарного сдвига могут выполнять некоторые арифметические вычисления намного быстрее, чем традиционные операторы.
Пример использования побитовых и бинарных сдвигов:
- Создайте новую папку и консольное приложение с именем
BitwiseAndShiftOperators
и добавьте его в рабочую область. - Добавьте в метод
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-битовые колонки } }
- Запустите консольное приложение и обратите внимание на результаты:
a = 10 b = 6 a & b = 2 a | b = 14 a ^ b = 12
- Добавьте в метод
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}"); } }
- Запустите консольное приложение и обратите внимание на результаты:
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#»
Задание 1
Напишите программу, которая принимает два числа от пользователя и выводит на экран результат их сложения, вычитания, умножения и деления.
Задание 2
Напишите программу, которая проверяет, является ли введенное число четным или нечетным.
Задание 3
Напишите программу, которая принимает два числа и выводит сообщение о том, какое из них больше, меньше или равны они.
Задание 4
Напишите программу, которая использует тернарный оператор для определения, является ли число положительным, отрицательным или нулем.
Задание 5
Напишите программу, которая принимает число и сдвигает его биты влево на 2 позиции, а затем выводит результат.
using System;
class Program
{
static void Main()
{
// Задание 1: Арифметические операции
Console.Write("Введите первое число: ");
int num1 = int.Parse(Console.ReadLine());
Console.Write("Введите второе число: ");
int num2 = int.Parse(Console.ReadLine());
int sum = num1 + num2;
int diff = num1 - num2;
int product = num1 * num2;
double quotient = (double)num1 / num2;
Console.WriteLine($"Сумма: {sum}");
Console.WriteLine($"Разница: {diff}");
Console.WriteLine($"Произведение: {product}");
Console.WriteLine($"Частное: {quotient}");
// Задание 2: Проверка на четность
Console.Write("Введите число для проверки на четность: ");
int num = int.Parse(Console.ReadLine());
if (num % 2 == 0)
{
Console.WriteLine("Число четное.");
}
else
{
Console.WriteLine("Число нечетное.");
}
// Задание 3: Сравнение двух чисел
Console.Write("Введите первое число для сравнения: ");
int compareNum1 = int.Parse(Console.ReadLine());
Console.Write("Введите второе число для сравнения: ");
int compareNum2 = int.Parse(Console.ReadLine());
if (compareNum1 > compareNum2)
{
Console.WriteLine("Первое число больше второго.");
}
else if (compareNum1 < compareNum2)
{
Console.WriteLine("Первое число меньше второго.");
}
else
{
Console.WriteLine("Оба числа равны.");
}
// Задание 4: Тернарный оператор
Console.Write("Введите число для проверки знака: ");
int checkNum = int.Parse(Console.ReadLine());
string result = checkNum > 0 ? "Положительное" : (checkNum < 0 ? "Отрицательное" : "Ноль");
Console.WriteLine($"Число {result}.");
// Задание 5: Битовый сдвиг
Console.Write("Введите число для битового сдвига: ");
int shiftNum = int.Parse(Console.ReadLine());
int shifted = shiftNum << 2; // Сдвиг влево на 2 позиции
Console.WriteLine($"Число после битового сдвига влево на 2 позиции: {shifted}");
}
}
Объяснение кода
- Задание 1: Арифметические операции
- Пользователь вводит два числа.
- Производятся операции сложения, вычитания, умножения и деления.
- Результаты выводятся на экран.
- Задание 2: Проверка на четность
- Пользователь вводит число.
- Проверяется, делится ли число на 2 без остатка.
- Выводится сообщение о четности или нечетности числа.
- Задание 3: Сравнение двух чисел
- Пользователь вводит два числа.
- Сравниваются два числа и выводится результат сравнения.
- Задание 4: Тернарный оператор
- Пользователь вводит число.
- Тернарный оператор определяет, положительное это число, отрицательное или ноль.
- Результат выводится на экран.
- Задание 5: Битовый сдвиг
- Пользователь вводит число.
- Битовый сдвиг влево на 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
, не обязано ссылаться на одно значение.
Создание консольного приложения для изучения операторов ветвления
- Создайте папку и консольное приложение с именем
SelectionStatements
и добавьте его в рабочую область. - Добавьте следующие операторы внутрь метода
Main
для проверки наличия аргументов, переданных этому консольному приложению:using static System.Console; class Program { static void Main(string[] args) { if (args.Length == 0) { WriteLine("Аргументы отсутствуют."); } else { WriteLine("Присутствует как минимум один аргумент."); } } }
- Запустите консольное приложение, введя следующую команду в терминале:
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
в сочетании с объявлением локальной переменной, чтобы сделать ваш код безопаснее.
- Добавьте операторы в конец метода
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!"); } } }
- Запустите консольное приложение и посмотрите результаты:
o is not an int so it cannot multiply!
- Удалите двойные кавычки вокруг значения
"3"
, чтобы значение, хранящееся в переменнойo
, было типаint
, а не типаstring
.object o = 3;
- Перезапустите консольное приложение, чтобы увидеть результаты:
3 x 4 = 12
Разветвление с оператором switch
Оператор switch
отличается от оператора if
, потому что он сравнивает одно выражение с набором возможных значений (case). Каждый раздел case
должен завершаться одним из следующих способов:
- Ключевым словом
break
- Ключевыми словами
goto case
- Без операторов
- Ключевым словом
return
Написание кода с оператором switch:
- Вставьте операторы для оператора
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 } }
- Запуск консольного приложения. Запустите консольное приложение несколько раз, чтобы увидеть, что происходит в различных случаях случайных чисел. Пример вывода:
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
В этом примере мы будем использовать путь к папке и откроем файл в режиме только для чтения или для записи, в зависимости от ввода пользователя. В зависимости от типа и возможностей потока, мы выведем соответствующее сообщение.
- Добавьте следующую строку в начало файла, чтобы импортировать типы для работы с вводом/выводом:
using System.IO;
- Добавьте следующие операторы в конец метода
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); } }
- Запуск консольного приложения. Запустите консольное приложение и отметьте, что переменная
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 выражений, чтобы вы могли сравнить два стиля.
- Добавьте следующий код в конец метода
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);
- Полный код
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); } }
- Запуск консольного приложения
Запустите консольное приложение и убедитесь, что результат аналогичен тому, что был ранее. Использование 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#
Задание 6
Напишите программу, которая принимает число и выводит, положительное оно, отрицательное или ноль, используя оператор
if
.
Задание 7
Напишите программу, которая принимает день недели в виде числа (1-7) и выводит название дня недели, используя оператор
switch
.
Задание 8
Напишите программу, которая принимает значение типа
object
и выводит сообщение в зависимости от типа значения, используя сопоставление с образцом (pattern matching) в оператореif
.
Задание 9
Напишите программу, которая принимает значение типа
object
и выводит сообщение в зависимости от типа значения, используя сопоставление с образцом (pattern matching) в оператореswitch
.
Задание 10
Напишите программу, которая принимает оценку (A, B, C, D, F) и выводит соответствующее сообщение, используя выражение
switch
.
using System;
class Program
{
static void Main()
{
// Задание 6: Оператор if
Console.Write("Введите число: ");
int number = int.Parse(Console.ReadLine());
if (number > 0)
{
Console.WriteLine("Число положительное.");
}
else if (number < 0)
{
Console.WriteLine("Число отрицательное.");
}
else
{
Console.WriteLine("Число равно нулю.");
}
// Задание 7: Оператор switch
Console.Write("Введите номер дня недели (1-7): ");
int day = int.Parse(Console.ReadLine());
switch (day)
{
case 1:
Console.WriteLine("Понедельник");
break;
case 2:
Console.WriteLine("Вторник");
break;
case 3:
Console.WriteLine("Среда");
break;
case 4:
Console.WriteLine("Четверг");
break;
case 5:
Console.WriteLine("Пятница");
break;
case 6:
Console.WriteLine("Суббота");
break;
case 7:
Console.WriteLine("Воскресенье");
break;
default:
Console.WriteLine("Неверный номер дня недели.");
break;
}
// Задание 8: Сопоставление с образцом в if
Console.Write("Введите значение: ");
object value = Console.ReadLine();
if (value is int intValue)
{
Console.WriteLine($"Целое число: {intValue}");
}
else if (value is string strValue)
{
Console.WriteLine($"Строка: {strValue}");
}
else if (value is double doubleValue)
{
Console.WriteLine($"Дробное число: {doubleValue}");
}
else
{
Console.WriteLine("Неизвестный тип значения.");
}
// Задание 9: Сопоставление с образцом в switch
Console.Write("Введите значение: ");
object inputValue = Console.ReadLine();
switch (inputValue)
{
case int intValue:
Console.WriteLine($"Целое число: {intValue}");
break;
case string strValue:
Console.WriteLine($"Строка: {strValue}");
break;
case double doubleValue:
Console.WriteLine($"Дробное число: {doubleValue}");
break;
default:
Console.WriteLine("Неизвестный тип значения.");
break;
}
// Задание 10: Короткая запись switch (switch выражение)
Console.Write("Введите оценку (A, B, C, D, F): ");
char grade = char.Parse(Console.ReadLine().ToUpper());
string message = grade switch
{
'A' => "Отлично",
'B' => "Хорошо",
'C' => "Удовлетворительно",
'D' => "Плохо",
'F' => "Очень плохо",
_ => "Неверная оценка"
};
Console.WriteLine(message);
}
}
Объяснение кода
- Задание 6: Оператор if
- Пользователь вводит число.
- С помощью оператора
if
проверяется, положительное число, отрицательное или ноль. - Выводится соответствующее сообщение.
- Задание 7: Оператор switch
- Пользователь вводит номер дня недели (1-7).
- С помощью оператора
switch
определяется название дня недели и выводится на экран.
- Задание 8: Сопоставление с образцом в if
- Пользователь вводит значение.
- С помощью сопоставления с образцом (
is
) в оператореif
определяется тип значения и выводится соответствующее сообщение.
- Задание 9: Сопоставление с образцом в switch
- Пользователь вводит значение.
- С помощью сопоставления с образцом в операторе
switch
определяется тип значения и выводится соответствующее сообщение.
- Задание 10: Короткая запись switch (switch выражение)
- Пользователь вводит оценку (A, B, C, D, F).
- С помощью выражения
switch
определяется соответствующее сообщение и выводится на экран.
Циклы в C#
Операторы итерации повторяют блок операторов, пока условие истинно, или для каждого элемента в коллекции. Выбор оператора зависит от удобства понимания для решения задачи и личных предпочтений.
Цикл с оператором while
Оператор while
оценивает булево выражение и продолжает цикл, пока это выражение истинно.
Пример использования while
- Создайте новую папку и консольное приложение под названием
IterationStatements
и добавьте его в рабочее пространство. - Введите следующий код внутри метода
Main
:using static System.Console; class Program { static void Main(string[] args) { int x = 0; while (x < 10) { WriteLine(x); x++; } } }
- Запустите консольное приложение и посмотрите результаты, которые должны быть числами от 0 до 9:
0 1 2 3 4 5 6 7 8 9
Цикл с оператором do
Оператор do
похож на while
, за исключением того, что булево выражение проверяется внизу блока, а не вверху. Это означает, что блок выполняется как минимум один раз.
Пример использования do
- Введите следующий код в конец метода
Main
:string password = string.Empty; do { Write("Enter your password: "); password = ReadLine(); } while (password != "Pa$$w0rd"); WriteLine("Correct!");
- Запустите консольное приложение и обратите внимание, что вас будет неоднократно запрашивать на ввод пароля, пока вы не введете его правильно:
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!
- Как дополнительное задание, добавьте операторы, чтобы пользователь мог сделать только десять попыток перед выводом сообщения об ошибке.
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
- Введите оператор
for
для вывода чисел от 1 до 10:for (int y = 1; y <= 10; y++) { WriteLine(y); }
- Запустите консольное приложение и посмотрите результат, который должен быть числами от 1 до 10.
Цикл с оператором foreach
Оператор foreach
немного отличается от предыдущих трех операторов итерации. Он используется для выполнения блока операторов для каждого элемента в последовательности, например, массива или коллекции. Каждый элемент обычно доступен только для чтения, и если структура последовательности изменяется во время итерации, например, путем добавления или удаления элемента, то будет выброшено исключение.
Пример использования foreach
- Введите операторы для создания массива строковых переменных и вывода длины каждой из них:
string[] names = { "Adam", "Barry", "Charlie" }; foreach (string name in names) { WriteLine($"{name} has {name.Length} characters."); }
- Запустите консольное приложение и посмотрите результаты:
Adam has 4 characters. Barry has 5 characters. Charlie has 7 characters.
Как работает foreach изнутри
Технически оператор foreach
будет работать с любым типом, который соответствует следующим правилам:
- Тип должен иметь метод с именем
GetEnumerator
, который возвращает объект. - Возвращаемый объект должен иметь свойство с именем
Current
и метод с именемMoveNext
. - Метод
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
, не может использоваться для изменения значения текущего элемента.
Задания на тренировку использования циклов в C#
Задание 11
Напишите программу, которая выводит на экран числа от 1 до 10 с использованием цикла
for
.
Задание 12
Напишите программу, которая суммирует все элементы массива целых чисел с использованием цикла
foreach
.
Задание 13
Напишите программу, которая выводит на экран все четные числа от 1 до 20 с использованием цикла
while
.
Задание 14
Напишите программу, которая запрашивает у пользователя ввод числа до тех пор, пока он не введет положительное число, с использованием цикла
do-while
.
Задание 15
Напишите программу, которая вычисляет факториал числа, введенного пользователем, с использованием цикла
for
.
using System;
class Program
{
static void Main()
{
// Задание 11: Цикл for
Console.WriteLine("Числа от 1 до 10:");
for (int i = 1; i <= 10; i++)
{
Console.WriteLine(i);
}
// Задание 12: Цикл foreach
int[] numbers = { 1, 2, 3, 4, 5 };
int sum = 0;
foreach (int number in numbers)
{
sum += number;
}
Console.WriteLine($"Сумма элементов массива: {sum}");
// Задание 13: Цикл while
Console.WriteLine("Четные числа от 1 до 20:");
int n = 1;
while (n <= 20)
{
if (n % 2 == 0)
{
Console.WriteLine(n);
}
n++;
}
// Задание 14: Цикл do-while
int userInput;
do
{
Console.Write("Введите положительное число: ");
userInput = int.Parse(Console.ReadLine());
} while (userInput <= 0);
Console.WriteLine($"Вы ввели положительное число: {userInput}");
// Задание 15: Вычисление факториала с помощью цикла for
Console.Write("Введите число для вычисления факториала: ");
int factorialInput = int.Parse(Console.ReadLine());
long factorial = 1;
for (int i = 1; i <= factorialInput; i++)
{
factorial *= i;
}
Console.WriteLine($"Факториал числа {factorialInput} равен {factorial}");
}
}
- Задание 11: Цикл
for
- Используется цикл
for
, чтобы вывести числа от 1 до 10. - Цикл начинается с 1 и продолжается до 10 включительно, выводя каждое значение переменной
i
.
- Используется цикл
- Задание 12: Цикл
foreach
- Создается массив целых чисел.
- С помощью цикла
foreach
суммируются все элементы массива, и результат выводится на экран.
- Задание 13: Цикл
while
- С помощью цикла
while
выводятся все четные числа от 1 до 20. - Переменная
n
увеличивается на 1 в каждой итерации цикла, и проверяется, является ли она четной.
- С помощью цикла
- Задание 14: Цикл
do-while
- Цикл
do-while
используется для запроса ввода у пользователя до тех пор, пока не будет введено положительное число. - После завершения цикла выводится введенное положительное число.
- Цикл
- Задание 15: Вычисление факториала с помощью цикла
for
- Пользователь вводит число для вычисления его факториала.
- Используется цикл
for
для вычисления факториала введенного числа. - Результат выводится на экран.
Приведения и преобразования типов
Часто бывает необходимо преобразовывать значения переменных между различными типами. Например, данные могут вводиться в виде текста в консоли, поэтому они изначально хранятся в переменной типа string
, но затем их нужно преобразовать в дату/время, число или другой тип данных в зависимости от того, как они должны быть сохранены и обработаны.
Иногда требуется преобразование между числовыми типами, например, между целым числом и числом с плавающей точкой, перед выполнением вычислений.
Приведение (casting) бывает двух видов: неявное и явное. Неявное приведение происходит автоматически и безопасно, то есть вы не потеряете никакой информации. Явное приведение должно выполняться вручную, так как оно может потерять информацию, например, точность числа. При явном приведении вы сообщаете компилятору C#, что понимаете и принимаете риск.
Пример приведения чисел неявно и явно
Создание нового проекта
- Создайте новую папку и проект консольного приложения с именем
CastingConverting
и добавьте его в рабочее пространство.
Пример неявного приведения
- В методе
Main
введите операторы для объявления и присвоения целочисленной переменной и переменной с плавающей точкой, а затем неявно приведите значение целого числа при присвоении его переменной с плавающей точкой:int a = 10; double b = a; // целое число можно безопасно привести к числу с плавающей точкой WriteLine(b);
- В методе
Main
введите следующие операторы:double c = 9.8; int d = c; // компилятор выдаст ошибку для этой строки WriteLine(d);
Явное приведение
Вы не можете неявно привести переменную типа double
к переменной типа int
, так как это потенциально небезопасно и может привести к потере данных.
- Просмотрите окно ПРОБЛЕМ (Problems) и обратите внимание на сообщение об ошибке:
error CS0266: Cannot implicitly convert type 'double' to 'int'. An explicit conversion exists (are you missing a cast?)
- В окне ТЕРМИНАЛ введите команду
dotnet run
и обратите внимание на сообщение об ошибке:The build failed. Fix the build errors and run again.
Вы должны явно привести переменную типа
double
к переменной типаint
с помощью пары круглых скобок вокруг типа, к которому вы хотите привести значение. Эта пара круглых скобок называется оператором приведения. - Измените оператор присваивания для переменной
d
следующим образом:int d = (int)c; WriteLine(d); // d равно 9, теряя часть .8
- Запустите консольное приложение, чтобы увидеть результаты:
10 9
Приведение между большими и маленькими целыми числами
Необходимо выполнять аналогичную операцию при преобразовании значений между большими и маленькими целыми числами. Опять же, будьте осторожны, так как любое слишком большое значение будет интерпретировано неверно.
- Введите операторы для объявления и присвоения переменной
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}");
- Запустите консольное приложение, чтобы увидеть результаты:
e is 10 and f is 10 e is 9,223,372,036,854,775,807 and f is -1
- Измените значение
e
на 5 миллиардов:e = 5_000_000_000;
- Запустите консольное приложение, чтобы увидеть результаты:
e is 5,000,000,000 and f is 705,032,704
Преобразование с использованием типа System.Convert
Альтернативой использованию оператора приведения является использование типа System.Convert
. Этот тип может преобразовывать данные из всех числовых типов C#, а также логические значения, строки и значения даты и времени.
Давайте напишем код, чтобы увидеть это в действии:
- В начале файла
Program.cs
статически импортируйте классSystem.Convert
, как показано в следующем коде:using static System.Convert;
- Добавьте операторы в конец метода
Main
, чтобы объявить и присвоить значение переменной типаdouble
, преобразовать её в целое число и затем вывести оба значения в консоль:double g = 9.8; int h = ToInt32(g); WriteLine($"g is {g} and h is {h}");
- Запустите консольное приложение и просмотрите результат, который должен быть следующим:
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# этому правилу.
- В конце метода
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)); }
- Запустите консольное приложение и просмотрите результат:
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
.
- В конце метода
Main
добавьте операторы для округления каждого значения типаdouble
по правилу округления «от нуля» (AwayFromZero) и выведите результат в консоль: - Запустите консольное приложение и просмотрите результат:
Объяснение
- 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
преобразует текущее значение любой переменной в текстовое представление. Некоторые типы не могут быть адекватно представлены в виде текста, поэтому они возвращают свое пространство имен и имя типа.
Преобразование типов в строку
- В конце метода
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());
- Запустите консольное приложение и просмотрите результат:
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
- В конце метода
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
- В методе
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}.");
- Запустите консольное приложение и просмотрите результат:
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
вызывает ошибку, если строка не может быть преобразована. Давайте рассмотрим пример:
- Добавьте оператор в конец метода
Main
для попытки преобразования строки, содержащей буквы, в целое число:int count = int.Parse("abc");
- Запустите консольное приложение и просмотрите результат:
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).
Примеры работы с датами в российских стандартах
- Установка культуры для текущего потока
Для корректного форматирования дат согласно российским стандартам, можно установить русскую культуру для текущего потока:
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}");
}
}
- Разбор строки с датой в российском формате
Для разбора строки с датой в российском формате можно использовать метод 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}");
}
}
- Форматирование даты в строку
Для форматирования даты в строку в российском формате можно использовать метод ToString
с указанием формата:
using System;
class Program
{
static void Main()
{
DateTime now = DateTime.Now;
string formattedDate = now.ToString("dd.MM.yyyy");
Console.WriteLine($"Formatted date: {formattedDate}");
}
}
- Использование
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. Давайте посмотрим, как это работает на практике:
- Создайте папку и консольное приложение с именем
HandlingExceptions
и добавьте его в рабочую область. - В методе 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"); } }
Этот код включает два сообщения, чтобы указать на начало и конец процесса преобразования, что делает более понятным поток выполнения кода. Это особенно полезно по мере усложнения примера.
- Запустите консольное приложение.
- Введите корректный возраст, например, 47, и посмотрите результат:
Before parsing What is your age? 47 You are 47 years old. After parsing
- Запустите консольное приложение снова.
- Введите некорректный возраст, например, «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:
- Добавьте объявление переменной исключения в блок catch и используйте её для вывода информации об исключении в консоль, как показано в следующем коде:
catch(Exception ex) { Console.WriteLine($"{ex.GetType()} says {ex.Message}"); }
- Запустите консольное приложение.
- Введите некорректный возраст, например, «kermit», и посмотрите результат:
Before parsing What is your age? kermit System.FormatException says Input string was not in a correct format. After parsing
Обработка конкретных исключений
Теперь, когда мы знаем, какой конкретный тип исключения произошел, мы можем улучшить наш код, ловя именно этот тип исключения и настраивая сообщение, которое отображается пользователю:
- Оставьте существующий блок 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}"); }
- Запустите консольное приложение.
- Введите некорректный возраст, например, «kermit», и посмотрите результат:
Before parsing What is your age? kermit The age you entered is not a valid number format. After parsing
Мы оставляем более общий блок catch ниже, потому что могут возникнуть другие типы исключений.
- Запустите консольное приложение снова.
- Введите число, которое слишком велико для типа
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}");
}
- Запустите консольное приложение.
- Введите слишком большое число, и посмотрите результат:
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 в неправильном порядке.
Задания на закрепление темы конвертации типов данных в C#
Задание 16: Неявное приведение типов
Напишите программу, которая демонстрирует неявное приведение типа
int
к типуdouble
.
Задание 17: Явное приведение типов
Напишите программу, которая демонстрирует явное приведение типа
double
к типуint
.
Задание 18: Преобразование при помощи System.Convert
Напишите программу, которая преобразует строку в целое число с использованием класса
System.Convert
.
Задание 19: Округление чисел (математическое и банковское)
Напишите программу, которая демонстрирует математическое округление и банковское округление числа.
Задание 20: Преобразование любого типа в строку
Напишите программу, которая преобразует различные типы данных (int, double, decimal, datetime) в строку и выводит их на экран.
Задание 21: Форматирование и преобразование дат
Напишите программу, которая преобразует строку в дату и выводит ее в различных форматах.
Задание 22: Конвертация бинарных объектов
Напишите программу, которая преобразует массив байт в строку в формате Base64 и обратно.
using System;
class Program
{
static void Main()
{
// Задание 16: Неявное приведение типов
int intValue = 10;
double doubleValue = intValue; // Неявное приведение int к double
Console.WriteLine($"Неявное приведение: int {intValue} к double {doubleValue}");
// Задание 17: Явное приведение типов
double pi = 3.14;
int truncatedPi = (int)pi; // Явное приведение double к int
Console.WriteLine($"Явное приведение: double {pi} к int {truncatedPi}");
// Задание 18: Преобразование при помощи System.Convert
string strNumber = "123";
int convertedNumber = Convert.ToInt32(strNumber); // Преобразование строки в целое число
Console.WriteLine($"Преобразование строки \"{strNumber}\" в число {convertedNumber}");
// Задание 19: Округление чисел (математическое и банковское)
double number = 12.5;
double roundedMath = Math.Round(number); // Математическое округление
double roundedBankers = Math.Round(number, MidpointRounding.ToEven); // Банковское округление
Console.WriteLine($"Математическое округление числа {number}: {roundedMath}");
Console.WriteLine($"Банковское округление числа {number}: {roundedBankers}");
// Задание 20: Преобразование любого типа в строку
int intNumber = 42;
bool boolValue = true;
DateTime currentDate = DateTime.Now;
object someObject = new object();
Console.WriteLine($"Целое число как строка: {intNumber.ToString()}");
Console.WriteLine($"Булево значение как строка: {boolValue.ToString()}");
Console.WriteLine($"Текущая дата и время как строка: {currentDate.ToString()}");
Console.WriteLine($"Объект как строка: {someObject.ToString()}");
// Задание 21: Форматирование и преобразование дат
string dateString = "15 July 2024";
DateTime date = DateTime.Parse(dateString);
Console.WriteLine($"Дата в коротком формате: {date.ToShortDateString()}");
Console.WriteLine($"Дата в длинном формате: {date.ToLongDateString()}");
Console.WriteLine($"Дата и время в стандартном формате: {date.ToString()}");
// Задание 22: Конвертация бинарных объектов
byte[] binaryData = new byte[10];
new Random().NextBytes(binaryData);
string base64String = Convert.ToBase64String(binaryData);
Console.WriteLine($"Массив байт в формате Base64: {base64String}");
byte[] originalData = Convert.FromBase64String(base64String);
Console.WriteLine($"Преобразованный обратно массив байт:");
foreach (byte b in originalData)
{
Console.Write($"{b:X2} ");
}
Console.WriteLine();
// Обработка исключений при преобразовании типов
Console.WriteLine("Введите число:");
string input = Console.ReadLine();
try
{
int parsedNumber = int.Parse(input);
Console.WriteLine($"Вы ввели число: {parsedNumber}");
}
catch (FormatException)
{
Console.WriteLine("Ошибка: введенная строка не является числом.");
}
catch (Exception ex)
{
Console.WriteLine($"Неожиданная ошибка: {ex.Message}");
}
}
}
- Задание 16: Неявное приведение типов
- Демонстрируется неявное приведение типа
int
кdouble
.
- Демонстрируется неявное приведение типа
- Задание 17: Явное приведение типов
- Пример явного приведения типа
double
кint
, где дробная часть отбрасывается.
- Пример явного приведения типа
- Задание 18: Преобразование при помощи System.Convert
- Преобразование строки в целое число с использованием
Convert.ToInt32
.
- Преобразование строки в целое число с использованием
- Задание 19: Округление чисел (математическое и банковское)
- Пример математического и банковского округления числа.
- Задание 20: Преобразование любого типа в строку
- Преобразование различных типов данных в строку и вывод их на экран.
- Задание 21: Форматирование и преобразование дат
- Преобразование строки в дату и вывод ее в различных форматах.
- Задание 22: Конвертация бинарных объектов
- Преобразование массива байт в строку в формате Base64 и обратно.
Проектные задания (идеи для pet проектов)
- Мониторинг серверов для системного администратора (DevOps):
- Программа позволяет системному администратору быстро проверять доступность серверов в сети.
- Она использует файл со списком IP-адресов и имен серверов для проверки доступности каждого из них через пинг.
- Администратор получает отчет о статусе серверов (в сети или не доступен).
- Калькулятор для учителя математики:
- Приложение генерирует случайные арифметические примеры и проверяет правильность ответов учеников.
- Учитель может использовать его для быстрой подготовки заданий и проверки знаний учеников.
- Программа поддерживает сложение, вычитание, умножение и деление.
- Учет товаров для продавца стройматериалов:
- Приложение помогает продавцу вести учет товаров на складе, добавлять новые позиции, удалять и обновлять количество товаров.
- Продавец может легко просматривать текущий список товаров и управлять складскими запасами.
- Программа помогает поддерживать порядок и точность учета на складе.
- Расчет прочности материалов для инженера:
- Программа позволяет инженеру быстро рассчитывать максимальную нагрузку, которую может выдержать материал, исходя из его типа и размеров.
- Это полезно для проектирования и оценки безопасности конструкций.
- Программа использует условные значения прочности для различных материалов и рассчитывает нагрузку на основе размеров.
- Планировщик задач для сценариста:
- Приложение помогает сценаристу управлять своими задачами, устанавливать дедлайны и отслеживать выполнение.
- Сценарист может добавлять новые задачи, помечать их как выполненные и просматривать текущий список задач.
- Программа помогает организовать работу и следить за сроками выполнения.