Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Основы объектно ориентированного программирования на языке С# базовые сведения о языке. Учебное пособие
.pdf
мы даже не упомянем в этом пособии). Например, можно вместо конструк-
ции !( a is double) написать a is not double. Кроме того, что гораздо нужнее,
можно определить новую переменную с явно заданным типом, куда скопируется значение исходной. Например, оператор
var b = a is double с;
не только присваивает значение переменной b, но и описывает переменную c типа double, которая принимает значение переменной a, если выражение истинно (при ложности выражения значение переменной c является
неопределенным). До введения такого синтаксиса приходилось то же самое записывать через явное преобразование типа, например, так:
var b = a is double;
double c;
if (b) c = (double)a;
Заметим, что существует еще оператор as явного преобразования
типа, который позволяет для типа T написать
var c = a as T;
В этом случае если переменная a реально не содержит значение типа T,
переменная с получит значение null. Если же в приведенном выше фрагменте с проверкой убрать проверку и написать
var c = (double) a;
то при невозможности приведения типа происходит исключение.
3.8. Оператор switch
Оператор предназначен для выбора одного условия из многих. Начиная с 9.0 версии языка, он имеет вид
21

switch(<выражение>)
{
case <шаблон соответствия >:<один или несколько операторов><оператор
завершения case>
…
default: <один или несколько операторов><оператор завершения case>
};
Сase-блоков может быть несколько, но не менее одного, default-блок может отсутствовать. Если по нескольким шаблонам соответствия требуется
выполнить одинаковые действия, то такие шаблоны соответствия могут
просто следовать друг за другом, как в примере ниже. Оператор switch выбирает первый шаблон соответствия, подходящий для заданного выражения, и выполняет операторы, соответствующего шаблону case-блока.
Последним оператором блока, оператором завершения case, обязательно
должен быть оператор передачи управления, это либо оператор break
(выйти из оператора switch), либо оператор goto case <шаблон соответствия>, который позволяет выполнить операторы другого блока, либо любой другой оператор, передающий управление вовне оператора switch
(например, оператор return выхода из функции). Если ни один шаблон соответствия не выполнился для выражения, то выполняется default-блок.
Шаблоны соответствия могут быть следующих видов.
1. Константа или выражение со значением, вычисляемым на этапе компи-
ляции. Шаблон выполняется при равенстве значения проверяемого выражения этой константе. Например, «case 12:».
2. Реляционный шаблон для сравнения результата выражения с констан-
той. В реляционном шаблоне можно использовать операции ‘<’, ‘>’,
‘<=’, ‘>=’. Например, «case <12:».
3. Логический шаблон для комбинации других шаблонов. Может исполь-
зовать ключевые слова and, or, not для булевых операций конъюнкции,
дизъюнкции и отрицания. Например, «case <12 or 15:».
4. Шаблон соответствия типу. Полностью эквивалентен оператору is, но
без ключевого слова is, например, «case int:» или «case int c:». Объявленную переменную можно использовать сразу после объявления в
этом же блоке.
5. Шаблон с охранными условиями. К любой разновидности шаблона,
если он не однозначно определяет соответствие проверяемому выражению, можно добавить любое дополнительное логическое условие с
22

обычным синтаксисом через ключевое слово when, например, «case int
c when IsPrime(c):».
Обратим внимание, что оператор switch всегда не менее эффективен,
чем серия проверок оператором if, а при константных шаблонах выполняется существенно быстрее.
Например:
switch (a)
{
case 1:
case 10:
Console.WriteLine(1);
break;
case < 11:
Console.WriteLine(2);
break;
case double and (< 10.0 or > 100):
Console.WriteLine(3);
break;
default:
Console.WriteLine(4);
break;
}
В тех ситуациях, когда в зависимости от наборов условий нужно получить всего одно значение, а не набор действий, можно использовать вместо оператора switch выражение switch с чуть более простым синтаксисом.
Выражение switch имеет вид: <проверяемое выражение> switch {<шаблон
соответствия> => <результат>, <шаблон соответствия> => <результат>, _ => <результат>}, где <шаблон соответствия> может быть та-
ким же, как в операторе switch (но не должно быть ключевого слова case,
а вместо двоеточия – ‘=>’), результат – выражение со значением одинакового типа для всех шаблонов соответствия (этот тип и будет типом выражения switch), символ подчеркивания играет роль блока default (и, в отличие от оператора switch, он должен быть обязательно последним). Например:
var s = a switch
{
< 11 => 10,
> 12 and < 15 => 15,
> 15 when a <= 100 => 17,
_ => 0,
};
23

Заметим, что компилятор и для оператора, и для выражения switch
проверяет, что все возможные значения проверяемого выражения покрыты, и, если это не так, выдает предупреждение при отсутствии блока
default для оператора и ошибку для выражения.
3.9. Оператор безусловного перехода
Оператор перехода похож на аналогичные операторы других языков,
имеет вид goto <метка>, где метка – идентификатор, которым помечен
какой-либо оператор, находящийся в том же блоке операторов или в одном
из охватывающих блоков внутри того же метода. Метка отделяется от оператора символом ‘:’.
Оператор вызывает переход управления на помеченный оператор, при
этом все покидаемые блоки корректно заканчиваются (т. е. если при выходе из блока должны были выполниться какие-то специальные назначенные действия, например закрытие файлов при выходе из оператора using,
который мы рассмотрим в разделе 10.2, то эти действия выполнятся до помеченного оператора).
Заметим, что классики структурного программирования вообще не рекомендуют использовать этот оператор, но, на наш взгляд, выйти, например, из вложенных циклов оператором перехода вполне корректно.
3.10. Исключения
В каком-то смысле еще одним способом передачи управления является механизм исключений. Механизм предназначен для обработки исключительных ситуаций. Он состоит из нескольких операторов. Оператор
throw инициирует исключение. Этот оператор имеет вид
throw <exception>;
где <exception> – выражение, приводимое к типу Exception. Оператор
throw немедленно прерывает выполнение программы и начинает сворачи-
вать стек вызовов до тех пор, пока не встретит ближайший оператор
try-catch-finally.
24

Оператор try-catch-finally имеет вид
try
{
<блок операторов с возможным вызовом подпрограмм, в которых может быть throw>
}
<несколько (возможно, ни одного) блоков catch>
<необязательный (если есть хоть один catch) блок finally>
Блок catch имеет вид
catch(<тип> <переменная>) when <условие>
{
<блок операторов>
}
Если возникает исключение внутри блока try (при выполнении оператора throw), то управление передается в блок операторов первого из блоков catch, к типу которого можно привести тип экземпляра, использованного в throw, и для которого выполняется условие when. Если это необходимо, в блоке операторов catch можно использовать оператор throw без
параметров, который означает немедленное завершение текущей обработки исключения и продолжение обработки выше по стеку вызовов.
Условие when может отсутствовать вместе с ключевым словом when.
Переменная в catch также может отсутствовать, если она не используется
в блоке операторов. Допустимо даже catch{ }, если нужно перехватить лю-
бое исключение.
Блок finally выполняется в любом случае, независимо от того, происходило исключение в блоке try или нет. Обычно в блоке finally программист освобождает ресурсы, выделенные в блоке try.
Заметим, что throw может быть не только оператором, но и выражением, что позволяет использовать его, например, так:
var c = a > 0 ? a : throw new Exception("отрицательное значение");

4. МЕТОДЫ (ПОДПРОГРАММЫ)
Идея разбивать программы на подпрограммы присутствует почти во
всех языках программирования. В C# подпрограммы больше принято
называть методами, но суть от этого не меняется: при вызове метода ему
передается управление, по окончании выполнения метода управление
обычно возвращается в точку вызова (если не произошло исключение).
Метод может иметь параметры, которые подставляются при вызове, и может возвращать значение, как функция.
Описание метода (без модификаторов, которые будут описаны чуть
позже) имеет вид: <тип возвращаемого значения> <идентифика-
тор> (<список параметров метода>) {<тело метода>}.
Если тело метода состоит из одного оператора или возвращает значение выражения, то может использоваться сокращенная форма: <тип воз-
вращаемого значения> <идентификатор> (<список параметров метода>) => <оператор или выражение>.
Обратим внимание, что тело метода действительно почти всегда присутствует в описании метода (за редкими исключениями, когда, например,
описываются интерфейсы или абстрактные методы, о которых читатель
узнает немного позже). Дело в том, что в C# метод необязательно должен
быть описан до его использования, как во многих других языках, компилятор «видит» все методы, описанные в любом файле проекта, одновременно.
Если метод не планируется никогда вызывать как функцию в выражениях и он не должен возвращать значение, то тип возвращаемого значения
должен описываться ключевым словом void.
Список параметров метода – разделенный запятыми список идентификаторов с указанием их типов, с возможным указанием значений по
умолчанию и способов передачи.
Обратим внимание, что язык допускает объявление методов с одинаковым именем и разным количеством или типами параметров. В этом случае при вызове метода компилятор выберет тот, которому список указанных при вызове параметров подходит больше всего. При этом методы не
могут отличаться только типом возвращаемого значения.
4.1. Ключевые слова out и ref
В отличие от С++, в котором параметры всегда передаются по значению (по значению того типа, который указан в описании параметра; если
26

параметр имеет тип «ссылка», то по значению ссылки), в C# есть дополнительные механизмы принудительной передачи параметра в метод по
ссылке, эти механизмы включаются специальными ключевыми словами
ref и out. Причем эти ключевые слова требуется указать не только при опи-
сании параметров метода, но и при его вызове, что позволяет программисту не перепутать способ передачи параметров (в С++ при передаче параметров-ссылок то, что передается ссылка, никак не указывается при вызове, что затрудняет программисту понимание того, что значение передаваемого параметра может измениться в подпрограмме). Например, метод
void Mult2(int a, out int b){ b = a * 2;}
удваивает значение параметра a и возвращает результат через параметр b,
передаваемый по ссылке. Вызов такого метода может иметь вид
Mult2(x, out y);
если переменные x и у были описаны ранее как int. В принципе, возвращаемый через ссылку out параметр можно описать и непосредственно при
вызове, например, так:
Mult2(x, out int y);
или даже так:
Mult2(x, out var y);
Обратим внимание, что компилятор отслеживает присваивание всем
параметрам out перед выходом из метода, и если есть пути, при которых
значение не будет установлено, сообщает об ошибке.
Ключевое слово ref отличается от out тем, что передаваемый параметр
должен быть инициализирован до вызова метода, и внутри метода к нему
можно обращаться для получения значения (при попытке получить значения параметра out внутри метода компилятор выдаст сообщение об
ошибке). При этом метод может, но не обязан присваивать значение параметру ref.
4.2. Ключевое слово in
Есть еще один достаточно редко используемый способ передачи параметров по ссылке, с использованием ключевого слова in. Этот способ
27

используется, если программист хочет указать компилятору, что время копирования параметра при передаче по значению слишком велико и следует передать объект по ссылке, при этом гарантируется, что содержимое
передаваемого объекта внутри метода изменено не будет. При этом при
вызове метода ключевое слово можно не указывать, но тогда если фактически передаваемый параметр не будет иметь строго тот тип, который указан в описании метода, то произойдет неявное преобразование типа (если
оно возможно) и по ссылке будет передана копия объекта. Если же при
вызове ключевое слово in указано, то компилятор при попытке передачи
не точно соответствующего типа выдаст сообщение об ошибке.
4.3. Параметры со значениями по умолчанию
У любого входного параметра (т. е. не помеченного ref или out) может
быть задано значение по умолчанию. Значение такого параметра можно не
задавать при вызове метода. Например, если метод имеет вид
int Sum(int a, int b = 10) => a + b;
то обратиться к нему можно как Sum (10), так и Sum (5, 7).
Значения по умолчанию могут иметь только последние параметры метода, т. е. если у какого-то параметра задано значение по умолчанию, то у
всех последующих параметров тоже должны быть заданы значения по
умолчанию.
4.4. Позиционные и ключевые параметры
При вызове метода параметры можно задавать в той последовательности, в которой они перечислены при описании метода. Но есть возможность менять порядок задания параметров или не задавать какие-то параметры, если у них есть значения по умолчанию. Для этого указывается имя
параметра и через двоеточие – его значение. Например, Sum (b:7, a:5).
Можно смешивать позиционное и ключевое задание параметров, т. е. несколько первых параметров задать позиционно, а остальные – по ключу.
28

4.5. Переменное число параметров
В С++ при описании функции можно указать переменное число параметров символом «многоточие» и потом «добывать» их внутри функции
достаточно сложным механизмом. C# – чуть более строго типизированный
и более удобный язык, поэтому переменное число параметров организуется следующим образом. Последним в списке параметров метода указывается ключевое слово params, и далее описывается одномерный массив
произвольного типа (подробнее организацию массивов в языке мы поясним в разделе 6). В этом случае при обращении к методу можно указать
либо массив того же типа, либо перечислить произвольное число параметров (даже ноль), конвертируемых неявно к этому типу. Внутри метода
число реально переданных параметров определяется длиной массива
(в C# можно узнать длину массива через его свойство Length). Например,
для вычисления минимума из произвольного количества целых чисел
можно написать метод:
int Min(params int[] array)
{
int min = int.MaxValue;
for (int i = 0; i < array.Length; i++)
{
if (array[i] < min)
min = array[i];
}
return min;
}
Обратиться к этому методу можно, например, так:
var c = Min(5, 4, -1);
в этом случае с примет значение «–1». Заметим, что константа
int.MaxValue имеет значение максимально представимого числа типа int, а
что она означает синтаксически, станет ясно чуть позже, после раздела 5.5.
29

4.6. Оператор return
Оператор имеет вид return <выражение>, где выражение должно
быть преобразуемым к типу возвращаемого значения метода. Если это тип
void, то выражения быть не должно. Оператор фактически является разновидностью оператора безусловного перехода, осуществляющего переход
на конец метода. Если метод не возвращает значений, то оператор return
не является обязательным. Если метод возвращает значение, то компилятор проверяет, что при всех путях выполнения метода (если в методе не
произошло исключений) это значение будет сформировано оператором re-
turn.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
