C#. Урок 6. Условные операторы и циклы

Для управления процессом выполнения программы C# предоставляет условные операторы if и switch, а для запуска повторяющихся действий циклы: for, while (do/while) и foreach. Этим конструкциям, а также знакомству с LINQ посвящен данный урок.

Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.

Условные операторы

Условные операторы – это конструкции, позволяющие управлять ходом выполнения программы в зависимости от определенных условий. В языке C# присутствует два типа таких конструкций: if…else и switch…case.

Оператор if

Конструкция if…else позволяет проверить некоторое условие на истинность и, в зависимости от результата, выполнить тот или иной блок кода. Синтаксис условного оператора if…else:

if (Условие_1)
{
// Блок выполнится, если Условие 1 имеет значение true
}  
else if (Условие_2) // Необязательная часть
{
// Блок выполнится, если Условие 2 имеет значение true
}
else // Необязательная часть
{
// Блок выполнится, если Условие 1 и Условие 2 имеют значение false
}

В качестве условия может выступать переменная типа bool, либо выражение (см. “Тип данных Boolean“), значение которого имеет тип bool.

Пример работы с оператором if:

int n1 = 9;
int n2 = 12;
if (n1 < n2)
{
    Console.WriteLine($"Число {n1} меньше числа {n2}");
}

В приведенном выше примере выражение 9 < 12 имеет значение true, поэтому выполняется блок кода после оператора if. Добавим конструкцию else и зададим значения переменным n3 и n4, при которых выражение, определяющее условие, будет иметь значение false:

int n3 = 15;
int n4 = 12;
if (n3 < n4)
{
    Console.WriteLine($"Число {n3} меньше числа {n4}");
}
else
{
    Console.WriteLine($"Число {n3} больше числа {n4}");
}

Для отдельного контроля варианта, когда сравниваемые числа равны, можно воспользоваться конструкцией if else:

int n5 = 13;
int n6 = 12;
if (n5 < n6)
{
    Console.WriteLine($"Число {n5} меньше числа {n6}");
}
else if (n5 > n6)
{
    Console.WriteLine($"Число {n5} больше числа {n6}");
}
else
{
    Console.WriteLine($"Число {n5} равно числу {n6}");
}

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

Оператор switch

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

Синтаксис конструкции имеет следующий вид:

switch (Выражение)
{
      case Шаблон_1:
// Блок кода выполняется, если Выражению соответствует Шаблон_1
          break;
      case Шаблон_2:
// Блок кода выполняется, если Выражению соответствует Шаблон_2
          break;
// ...
      default:
// Блок кода выполняется, если среди перечисленных в case шаблонах 
// нет подходящего
          break;
}

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

  • char;
  • string;
  • bool;
  • целочисленное значение;
  • enum.

С# 7 и более поздние версии поддерживают упомянутый выше шаблон констант и другие виды шаблонов.

Рассмотрим простой пример работы с switch:         

int n7 = 1;
switch (n7)
{
case 1:
    Console.WriteLine("Case 1");
    break;
case 2:
    Console.WriteLine("Case 2");
    break;
default:
    Console.WriteLine("Default case");
    break;
}

Оператор switch ищет совпадение значения переменной n7 со значениями, указанными после case. Если совпадение найдено, то выполняется код внутри блока case. Блок case может закончится оператором:

  • break – прерывание кода в конструкции switch;
  • goto case – используется если необходимо перейти в другой case;
  • return – завершает выполнение метода и возвращает указанное значение;
  • throw – используется для выброса исключения.

Если среди шаблонов в case не было найдено подходящего, то будет выполнен код в блоке default.

В качестве шаблонов в case могут выступать:

  • шаблоны констант;
  • шаблоны типов;
  • выражение с ключевым словом when.

Рассмотрим их более подробно.

Шаблон константы

Шаблон константы предполагает, что мы сравниваем значение выражение из switch с константами, которые располагаются в блоках case. Как уже было сказано выше, в таком варианте работы switchcase допустимы константы следующих типов:char, string, bool, целочисленное значение и enum. В первом примере, демонстрирующим работу оператора switch, уже был приведен вариант с шаблоном константы, приведем еще один, на этот раз, это будет работа со строками:

string s1 = "Three";
switch (s1.ToLower())
{
    case "one": Console.WriteLine("Case one"); break;
    case "two": Console.WriteLine("Case two"); break;
    case "three": Console.WriteLine("Case three"); break;
    case "four": Console.WriteLine("Case four"); break;
    case "five": Console.WriteLine("Case five"); break;
    default: Console.WriteLine("Default case"); break;
}

Шаблон типа

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

case type var_name

Если результат выражения можно привести к указанному типу, то полученное значение присваивается переменной с указанными именем – var_name.

Для задания альтернативы со значением null используйте следующий формат:

case null

В документации Microsoft приводится список условий, при которых результат выражения будет успешно приведен к указанному в case типу, перечислим некоторые из них:

  • выражение имеет указанный тип type;
  • результат выражения реализует интерфейс type;
  • результат выражения имеет тип производный от type.

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

class Transport { public string Name { get; set; } } 
class Bicycle : Transport { } 
class Moto : Transport { } 
class Car : Transport { }

Оператор switch…case, работающий с этими классами, может выглядеть следующим образом:

object trans = new Moto() { Name = "Suzuki" };
switch (trans)
{
    case Bicycle bicycle: Console.WriteLine($"Bicycle: {bicycle.Name}"); break;
    case Moto moto: Console.WriteLine($"Moto: {moto.Name}"); break;
    case Car car: Console.WriteLine($"Car {car.Name}"); break;
    case Transport transport: Console.WriteLine($"Transport {transport.Name}"); break;
    case null: Console.WriteLine("Transport is null!"); break;
}

Выражение с ключевым словом when

Язык C#, начиная с версии 7.0, позволяет использовать в switch…case предложение when для реализации возможности использования дополнительного условия. В этом случае типы после ключевого слова case могут совпадать и дополнительная фильтрация будет производиться по условию после when.

Дополним класс Transport, который мы создали в разделе “Шаблон типа”, свойством WheelsCount:

class Transport
{
    public string Name { get; set; }
    public int WheelsCount { get; set; }
}

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

object bc = new Bicycle() { Name = "Trec", WheelsCount = 1 };
switch (bc)
{
    case Bicycle bicycle when bicycle.WheelsCount == 1: Console.WriteLine($"Bicycle: {bicycle.Name}, type - monocycle"); break;
    case Bicycle bicycle when bicycle.WheelsCount == 2: Console.WriteLine($"Bicycle: {bicycle.Name}, type - classic"); break;
    case Bicycle bicycle when bicycle.WheelsCount == 3: Console.WriteLine($"Bicycle: {bicycle.Name}, type - tricycle"); break;
    case Moto moto: Console.WriteLine($"Moto: {moto.Name}"); break;
    case Car car: Console.WriteLine($"Car {car.Name}"); break;
    case Transport transport: Console.WriteLine($"Transport {transport.Name}"); break;
    case null: Console.WriteLine("Transport is null!"); break;
}

Тернарный оператор

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

Синтаксис оператора имеет следующий вид:

condition ? if_true : if_else

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

int n8 = 5;
int n9 = 8;
int largerNumber = n8 > n9 ? n8 : n9;

В примере, с помощью оператора ? определяется, какое из двух чисел больше, и соответствующее значение, присваивается переменной largerNumber.

Циклы

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

Цикл for

Цикл for имеет следующий синтаксис:

for (инициализация счетчика; условие; итератор)
{
    // оператор (набор операторов)
}

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

Условие – это выражение, которое проверяется перед каждой новой итерацией цикла. Если значение выражения равно true, то будет выполнено тело цикла, если false – произойдет выход из цикла и выполнится следующая за ним инструкция.

Итератор – это выражение, вычисляемое после каждой итерации. Обычно здесь происходит изменение локальной переменной, объявленной в инициализаторе.

Перечисленные выражения: инициализатор, условие и итератор разделяются символом “точка с запятой”. Цикл for удобно использовать, когда известно количество повторений. Пример работы с циклом for:

for (int i = 0; i < 3; i++) { 
    console.writeline(“Квадрат числа i ” + i*i); 
}

В нем мы инициализируем локальную переменную значением 0, затем проверяем, что переменная меньше трех, выполняем тело цикла – выводим в консоль значение числа i в квадрате. На каждой итерации прибавляем к переменной i значение 1, снова проверяем условие и выполняем тело цикла, и так, до тех пор, пока условие (i < 3) будет истинным.

Циклы while и do/while

В C# цикл while имеет следующую конструкцию:

while (условие)
{
// Тело цикла
}

В этом цикле проверяется условие, и если оно истинно, то выполняется набор операторов внутри тела цикла. Обязательно убедитесь, что изменяете переменную, от которой зависит условие, иначе цикл станет бесконечным, если, конечно, это не является целью. Пример работы с циклом while:

int i = 0;
int number = 3;
while(i <= number) {
    Console.WriteLine("Итерация цикла номер " + i);
    i++;
}
Console.ReadKey();

Цикл do/while имеет следующий синтаксис:

do {
//Тело цикла
} while (условие);

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

Пример работы с циклом do/while:

int j = 0;
int number2 = 3;
do
{
    Console.WriteLine("Итерация цикла номер " + j);
    j++;
} while (j > number2);
Console.ReadKey();

Цикл foreach

Последний вариант цикла, который мы рассмотрим в этом уроке – это foreach. Синтаксис оператора foreach имеет следующий вид:

foreach (тип имя_переменной_цикла in коллекция) 
{
// Тело цикла
}

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

  • реализует интерфейс  System.Collections.IEnumerable или System.Collections.Generic.IEnumerable<T>;
  • реализует открытый метод GetEnumerator, возвращающий интерфейс, класс или структуру, имеющие открытое свойство Current и метод MoveNext.

Пример работы с оператором foreach:

int[] nums = { 6, 3, 6, 8, 9, 12, 4, 5, 88, 54, 3, 66, 78, 10, 12, 5, 7, 9, 3, 5 };
int result = 0;
foreach (int n in nums)
{
    if (n > 10)
    {
        result++;
    }
}
Console.WriteLine($"Количество чисел в массиве больше 10: {result}");

В нем мы определяем количество чисел, которые больше десяти, в исходном массиве.

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

result = 0;
foreach (var n in nums)
{
    if (n < 10)
    {
        result++;
    }
}
Console.WriteLine($"Количество чисел в массиве меньше 10: {result}");

Операторы перехода

Язык C# предоставляет специальные операторы для прерывания выполнения всего цикла и для принудительного завершения текущей итерации с переходом к следующей. Первую задачу решает оператор break. Если в программе используется несколько вложенных циклов, то при использованиии break, выход будет выполнен только из того цикла, где этот оператор был вызван. Оператор continue принудительно завершает текущую итерацию цикла и переходит к следующей. При этом для цикла while и do/while происходит переход к условному выражению, а в цикле for сначала  вычисляется итерационное выражение, а затем проверяется условие:

Console.WriteLine("### Операторы перехода");
for (int i = 0; i < nums.Length; i++)
{
    if (nums[i] > 10)
    {
        Console.WriteLine($"{nums[i]} > 10");
        continue;
    }
    if (i > 7)
    {
        Console.WriteLine("Break cycle");
        break;
    }                  
}

LINQ как инструмент обхода коллекций

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

Ниже представлены два примера, первый – это вариант реализации некоторого алгоритма с использованием циклов, второй – этот же алгоритм, но решение построено с помощью LINQ.

Пример без LINQ

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

var evenSq = new List<int>();
foreach (var n in nums)
{
    if (n % 2 == 0)
    {
        evenSq.Add(n * n);
    }
}

Пример с LINQ

Выполним туже операцию с помощью LINQ:

evenSq = nums
    .Where(v => v % 2 == 0)
    .Select(v => v * v)
    .ToList();

С помощью методов расширения из System.Linq можно выполнять над коллекциями операции фильтрации, преобразования, группировки, агрегирования, формирования коллекций и т.д. Рассмотрим некоторые из этих возможностей.

Фильтрация

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

Извлечем из массива nums числа, значение которых больше 10:

var arr1 = nums.Where(v => v > 10).ToList(); // Take elements thats higher then 10

Преобразование

Метод Select применяет переданную ей функцию к элементам коллекции и формирует на базе полученных значений новую коллекцию. Вычтем из элементов nums константу 7:

var arr2 = nums.Select(v => v - 7).ToList(); // Sub 7 from every elements

Построим на базе nums новый массив, элементами которого будут bool значения: true, если соответствующий элемент в nums четный, false – в ином случае.

var arr3 = nums.Select(v => v % 2 == 0).ToList();

Рассмотрим ещё один пример работы с LINQ.

Создадим класс FootballTeams для представления футбольной команды: 

 public class FootballTeam
{
    public string Name { get; set; }
    public string Country { get; set; }
    public int Group { get; set; }
    public int PlaceGroup { get; set; }
}

Создадим метод для генерации данных о результатах футбольных матчей:

private static List<FootballTeam> CreateList()
{
    return new List<FootballTeam>
    {
        { new FootballTeam() { Name="Zenit", Country="Russia", Group= 1, PlaceGroup= 3, NumberpointsScored = 3}},
        { new FootballTeam() { Name="Ajax", Country="Holand", Group= 1, PlaceGroup= 2, NumberpointsScored = 4}},
        { new FootballTeam() { Name="Manchester United", Country="England", Group= 1, PlaceGroup= 1, NumberpointsScored = 6}},
        { new FootballTeam() { Name="Bavaria", Country="Germany", Group= 2, PlaceGroup= 1, NumberpointsScored = 8}},
        { new FootballTeam() { Name="Spartak", Country="Russia", Group= 2, PlaceGroup= 2, NumberpointsScored= 6}},
        { new FootballTeam() { Name="Real", Country="Italy", Group= 2, PlaceGroup= 3, NumberpointsScored = 3}},
        { new FootballTeam() { Name="Arsenal", Country="England", Group= 3, PlaceGroup= 2, NumberpointsScored = 9}},
        { new FootballTeam() { Name="Shakter", Country="Ukrane", Group=3, PlaceGroup= 3, NumberpointsScored = 6}},
        { new FootballTeam() { Name="Barselona", Country="Espane", Group= 3, PlaceGroup= 1, NumberpointsScored = 12}}
    };
}

Создадим переменную для хранения результатов матчей:

var teams = CreateList();

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

var teams = CreateList();           
var selectedTeams = from team in teams 
                    where team.PlaceGroup == 1 // фильтрация
                    orderby team.Name // упорядочивание по имени
                    select team; // выбор объекта
    foreach (var team in selectedTeams)
    {
        Console.WriteLine(team.Name + " " + team.NumberpointsScored);
    }

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

Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *