Классификация выражений await
Классификация выражений await t не отличается от классификации выражений (t).GetAwaiter().GetResult(). Например, если метод GetResult имеет тип возвращаемого значения void, выражение await классифицируется как Nothing. Если метод имеет тип возвращаемого значения T, отличный от void, выражение await классифицируется как значение типа T.
Вычисление выражений await во время выполнения
Во время выполнения выражение await t вычисляется следующим образом.
· Ожидающий объект a получается путем вычисления выражения (t).GetAwaiter().
· Значение b типа bool получается путем вычисления выражения (a).IsCompleted.
· Если значение b равно false, вычисление зависит от того, реализует ли объект a интерфейс System.Runtime.CompilerServices.ICriticalNotifyCompletion (далее для краткости называемый ICriticalNotifyCompletion). Эта проверка выполняется во время привязки, то есть во время выполнения, если объект a имеет тип времени компиляции dynamic, или во время компиляции во всех других случаях. Назовем r делегатом возобновления (§10.14):
o Если a не реализует интерфейс ICriticalNotifyCompletion, вычисляется выражение
(a as (INotifyCompletion)).OnCompleted(r).
o Если a реализует интерфейс ICriticalNotifyCompletion, вычисляется выражение
(a as (ICriticalNotifyCompletion)).UnsafeOnCompleted(r).
o Затем вычисление приостанавливается, и управление возвращается текущему вызывающему объекту асинхронной функции.
· Сразу вслед за этим (если значение b было равно true) или позднее, после вызова делегата возобновления (если значение b было равно false), вычисляется выражение (a).GetResult(). Если это выражение возвращает значение, данное значение является результатом выражения await. В противном случае результат равен Nothing.
Ожидающий объект должен реализовывать методы интерфейса INotifyCompletion.OnCompleted и ICriticalNotifyCompletion.UnsafeOnCompleted таким образом, чтобы делегат r вызывался не более одного раза. В противном случае поведение содержащей асинхронной функции будет неопределенным.
Арифметические операторы
Операторы *, /, %, + и – называются арифметическими операторами.
multiplicative-expression:
unary-expression
multiplicative-expression * unary-expression
multiplicative-expression / unary-expression
multiplicative-expression % unary-expression
additive-expression:
multiplicative-expression
additive-expression + multiplicative-expression
additive-expression – multiplicative-expression
Если операнд арифметического оператора имеет тип dynamic во время компиляции, то он динамически связан (§7.2.2). В этом случае тип времени компиляции выражения — dynamic, а разрешение, приведенное ниже, будет иметь место во время выполнения при использовании типа времени выполнения тех операндов, которые имеют тип dynamic во время компиляции.
Оператор произведения
Для операции вида x * y, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.3.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.
Ниже перечислены стандартные операторы произведения. Все операторы вычисляют произведение x и y.
· Произведение целых чисел:
int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
В контексте checked если произведение выходит за пределы диапазона типа результирующего значения, возникает исключение System.OverflowException. В контексте unchecked о переполнениях не сообщается, и все старшие биты, выходящие за пределы диапазона результирующего значения, отбрасываются.
· Произведение чисел с плавающей запятой
float operator *(float x, float y);
double operator *(double x, double y);
Произведение вычисляется в соответствии с арифметическими правилами стандарта IEEE 754. В следующей таблице приведены результаты всех возможных комбинаций ненулевых конечных значений, нулей, бесконечных значений и ошибок NaN. В таблице x и y являются положительными конечными значениями. z является результатом выражения x * y. Если результат слишком велик для целевого типа, то z равно бесконечности. Если результат слишком мал для целевого типа, то z равно нулю.
+y | –y | +0 | –0 | +∞ | –∞ | NaN | |
+x | +z | –z | +0 | –0 | +∞ | –∞ | NaN |
–x | –z | +z | –0 | +0 | –∞ | +∞ | NaN |
+0 | +0 | –0 | +0 | –0 | NaN | NaN | NaN |
–0 | –0 | +0 | –0 | +0 | NaN | NaN | NaN |
+∞ | +∞ | –∞ | NaN | NaN | +∞ | –∞ | NaN |
–∞ | –∞ | +∞ | NaN | NaN | –∞ | +∞ | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
· Произведение десятичных чисел.
decimal operator *(decimal x, decimal y);
Если итоговое значение слишком велико для представления в формате decimal, создается исключение System.OverflowException. Если полученное значение слишком мало для представления в формате decimal, результат равен нулю. Масштаб результата до округления равен сумме масштабов двух операндов.
Произведение десятичных чисел эквивалентно использованию оператора произведения типа System.Decimal.
Оператор деления
Для операции вида x / y, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.3.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.
Ниже перечислены стандартные операторы деления. Все операторы вычисляют частное x и y.
· Деление целых чисел:
int operator /(int x, int y);
uint operator /(uint x, uint y);
long operator /(long x, long y);
ulong operator /(ulong x, ulong y);
Если значение правого операнда равно нулю, возникает исключение System.DivideByZeroException.
Деление округляет результат в сторону нуля. Таким образом, абсолютным значением результата является наибольшее целое число, меньшее или равное абсолютному значению частного двух операндов. Результат равен нулю или положителен, когда два операнда имеют один знак, и равен нулю или отрицателен, когда два операнда имеют противоположные знаки.
Если левый операнд является самым малым представимым значением int или long, а правый операнд равен –1, происходит переполнение. В контексте checked это приводит к исключению System.ArithmeticException (или его подклассу). В контексте unchecked возникновение исключения System.ArithmeticException (или его подкласс) или выдача сообщения о переполнении (с передачей результирующего значения равным левому операнду) определяется реализацией.
· Деление чисел с плавающей запятой:
float operator /(float x, float y);
double operator /(double x, double y);
Частное вычисляется в соответствии с арифметическими правилами стандарта IEEE 754. В следующей таблице приведены результаты всех возможных комбинаций ненулевых конечных значений, нулей, бесконечных значений и ошибок NaN. В таблице x и y являются положительными конечными значениями. z является результатом выражения x / y. Если результат слишком велик для целевого типа, то z равно бесконечности. Если результат слишком мал для целевого типа, то z равно нулю.
+y | –y | +0 | –0 | +∞ | –∞ | NaN | |
+x | +z | –z | +∞ | –∞ | +0 | –0 | NaN |
–x | –z | +z | –∞ | +∞ | –0 | +0 | NaN |
+0 | +0 | –0 | NaN | NaN | +0 | –0 | NaN |
–0 | –0 | +0 | NaN | NaN | –0 | +0 | NaN |
+∞ | +∞ | –∞ | +∞ | –∞ | NaN | NaN | NaN |
–∞ | –∞ | +∞ | –∞ | +∞ | NaN | NaN | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
· Деление десятичных чисел:
decimal operator /(decimal x, decimal y);
Если значение правого операнда равно нулю, возникает исключение System.DivideByZeroException. Если итоговое значение слишком велико для представления в формате decimal, создается исключение System.OverflowException. Если полученное значение слишком мало для представления в формате decimal, результат равен нулю. Масштаб результата равен минимальному масштабу, который позволит сохранить результат равный ближайшему к истинному математическому результату представимому десятичному числу.
Деление десятичных чисел эквивалентно использованию оператора деления типа System.Decimal.
Оператор остатка
Для операции вида x % y, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.3.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.
Ниже перечислены стандартные операторы остатка. Все операторы вычисляют остаток деления x и y.
· Остаток для целых чисел:
int operator %(int x, int y);
uint operator %(uint x, uint y);
long operator %(long x, long y);
ulong operator %(ulong x, ulong y);
Результатом выражения x % y является значение, получаемое в результате вычисления x – (x / y) * y. Если y равно нулю, возникает исключение System.DivideByZeroException.
Если левый операнд является минимальным значением типа int или long, а правый операнд равен -1, то возникает исключение System.OverflowException. Если x % y не порождает исключения, x / y также не порождает исключения.
· Остаток для чисел с плавающей запятой:
float operator %(float x, float y);
double operator %(double x, double y);
В следующей таблице приведены результаты всех возможных комбинаций ненулевых конечных значений, нулей, бесконечных значений и ошибок NaN. В таблице x и y — положительные конечные значения. z является результатом x % y и вычисляется как x – n * y, где n является максимальным возможным целым числом, меньшим или равным x / y. Этот метод вычисления остатка аналогичен методу, используемому для целых операндов, но отличается от определения по стандарту IEEE 754 (в котором n является целым числом, ближайшим к x / y).
+y | –y | +0 | –0 | +∞ | –∞ | NaN | |
+x | +z | +z | NaN | NaN | x | x | NaN |
–x | –z | –z | NaN | NaN | –x | –x | NaN |
+0 | +0 | +0 | NaN | NaN | +0 | +0 | NaN |
–0 | –0 | –0 | NaN | NaN | –0 | –0 | NaN |
+∞ | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
–∞ | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
· Остаток для десятичных чисел:
decimal operator %(decimal x, decimal y);
Если значение правого операнда равно нулю, возникает исключение System.DivideByZeroException. Масштаб результат до округления равен большему масштабу из двух операндов, а знак результата (если он не равен нулю) равен знаку x.
Получение остатка для десятичных чисел эквивалентно использованию оператора остатка типа System.Decimal.
Оператор сложения
Для операции вида x + y, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.3.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.
Ниже перечислены стандартные операторы сложения. Для числовых типов и типов перечислений стандартные операторы сложения вычисляют сумму двух операндов. Когда один или оба операнда имеют строковый тип, стандартные операторы сложения выполняют сцепление строковых представлений операндов.
· Сложение целых чисел:
int operator +(int x, int y);
uint operator +(uint x, uint y);
long operator +(long x, long y);
ulong operator +(ulong x, ulong y);
В контексте checked, если сумма выходит за пределы диапазона типа результирующего значения, возникает исключение System.OverflowException. В контексте unchecked о переполнениях не сообщается, и все старшие биты, выходящие за пределы диапазона результирующего значения, отбрасываются.
· Сложение чисел с плавающей запятой:
float operator +(float x, float y);
double operator +(double x, double y);
Сумма вычисляется в соответствии с арифметическими правилами стандарта IEEE 754. В следующей таблице приведены результаты всех возможных комбинаций ненулевых конечных значений, нулей, бесконечных значений и ошибок NaN. В таблице x и y являются ненулевыми конечными значениями, а z является результатом выражения x + y. Если x и y имеют одинаковую величину, но противоположные знаки, то z равен положительному нулю. Если результат x + y слишком велик для представления в целевом типе, то z является бесконечным значением с таким же знаком, как и у x + y.
y | +0 | –0 | +∞ | –∞ | NaN | |
x | z | x | x | +∞ | –∞ | NaN |
+0 | y | +0 | +0 | +∞ | –∞ | NaN |
–0 | y | +0 | –0 | +∞ | –∞ | NaN |
+∞ | +∞ | +∞ | +∞ | +∞ | NaN | NaN |
–∞ | –∞ | –∞ | –∞ | NaN | –∞ | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN |
· Сложение десятичных чисел:
decimal operator +(decimal x, decimal y);
Если итоговое значение слишком велико для представления в формате decimal, создается исключение System.OverflowException. Масштаб результата до округления равен большему из масштабов двух операндов.
Получение суммы десятичных чисел эквивалентно использованию оператора сложения типа System.Decimal.
· Сложение элементов перечисления. В каждом типе перечисления неявно предоставляются следующие стандартные операторы, где E является типом перечисления, а U является базовым типом E:
E operator +(E x, U y);
E operator +(U x, E y);
Во время выполнения операторы вычисляются в точности как (E)((U)x + (U)y).
· Сцепление строк:
string operator +(string x, string y);
string operator +(string x, object y);
string operator +(object x, string y);
Эти перегрузки бинарного оператора + выполняют объединение строк. Если один операнд при сцеплении строк равен null, то подставляется пустая строка. Иначе любой нестроковый аргумент преобразуется в свое строковое представление путем вызова виртуального метода ToString, наследуемого от типа object. Если метод ToString возвращает значение null, подставляется пустая строка.
using System;
class Test
{
static void Main() {
string s = null;
Console.WriteLine("s = >" + s + "<"); // displays s = ><
int i = 1;
Console.WriteLine("i = " + i); // displays i = 1
float f = 1.2300E+15F;
Console.WriteLine("f = " + f); // displays f = 1.23E+15
decimal d = 2.900m;
Console.WriteLine("d = " + d); // displays d = 2.900
}
}
Результатом оператора сцепления строк является строка, состоящая из символов левого операнда, за которыми следуют символы правого операнда. Оператор сцепления строк никогда не возвращает значение null. При отсутствии памяти для размещения результирующей строки может возникать исключение System.OutOfMemoryException.
· Комбинация делегатов. В каждом типе делегата неявно предоставляется следующий стандартный оператор, где D имеет тип делегата:
D operator +(D x, D y);
Бинарный оператор + выполняет комбинацию делегатов, когда оба операнда имеют некоторый тип делегата D. (Если операнды имеют разные типы делегатов, возникает ошибка времени привязки.) Если первый операнд равен null, результат операции равен значению второго операнда (даже если оно также равно null). Иначе, если второй операнд равен null, то результатом операции является значение первого операнда. Иначе результатом операции является новый экземпляр делегата, который при вызове вызывает первый операнд, а затем второй операнд. Примеры комбинации операндов см. в разделах §7.8.5 и §15.4. Поскольку System.Delegate не является типом делегата, для него не определен operator +.
Оператор вычитания
Для операции вида x – y, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.3.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.
Ниже перечислены стандартные операторы вычитания. Все операторы выполняют вычитание y из x.
· Вычитание целых чисел:
int operator –(int x, int y);
uint operator –(uint x, uint y);
long operator –(long x, long y);
ulong operator –(ulong x, ulong y);
В контексте checked если разность выходит за пределы диапазона типа результирующего значения, возникает исключение System.OverflowException. В контексте unchecked о переполнениях не сообщается, и все старшие биты, выходящие за пределы диапазона результирующего значения, отбрасываются.
· Вычитание чисел с плавающей запятой:
float operator –(float x, float y);
double operator –(double x, double y);
Разность вычисляется в соответствии с арифметическими правилами стандарта IEEE 754. В следующей таблице приведены результаты всех возможных комбинаций ненулевых конечных значений, нулей, бесконечных значений и ошибок NaN В таблице x и y являются ненулевыми конечными значениями, а z является результатом выражения x – y. Если x и y равны, z равно положительному нулю. Если результат x – y слишком велик для представления в целевом типе, то z является бесконечным значением с таким же знаком, как и у x – y.
y | +0 | –0 | +∞ | –∞ | NaN | |
x | z | x | x | –∞ | +∞ | NaN |
+0 | –y | +0 | +0 | –∞ | +∞ | NaN |
–0 | –y | –0 | +0 | –∞ | +∞ | NaN |
+∞ | +∞ | +∞ | +∞ | NaN | +∞ | NaN |
–∞ | –∞ | –∞ | –∞ | –∞ | NaN | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN |
· Вычитание десятичных чисел.
decimal operator –(decimal x, decimal y);
Если итоговое значение слишком велико для представления в формате decimal, создается исключение System.OverflowException. Масштаб результата до округления равен большему из масштабов двух операндов.
Вычитание десятичных чисел эквивалентно использованию оператора вычитания типа System.Decimal.
· Вычитание элементов перечисления. В каждом типе перечисления неявно предоставляются следующий стандартный оператор, где E является типом перечисления, а U является базовым типом E:
U operator –(E x, E y);
Этот оператор вычисляется в точности как (U)((U)x – (U)y). Другими словами, оператор вычисляет разность между порядковыми значениями x и y, а типом результата является базовый тип перечисления.
E operator –(E x, U y);
Этот оператор вычисляется в точности как (E)((U)x – y). Другими словами, оператор вычитает значение из базового типа перечисления, выдавая значение перечисления.
· Удаление делегатов. В каждом типе делегата неявно предоставляется следующий стандартный оператор, где D имеет тип делегата:
D operator –(D x, D y);
Бинарный оператор – выполняет удаление делегатов, когда оба операнда имеют некоторый тип делегата D. Если операнды имеют разные типы делегатов, возникает ошибка времени привязки. Если первый операнд равен null, то результатом операции является null. Иначе, если второй операнд равен null, то результатом операции является значение первого операнда. Иначе оба операнда представляют списки вызова (§15.1) с одной или несколькими записями, а результатом является новый список вызова, состоящий из списка первого операнда, из которого удалены записи второго операнда, при условии что список второго операнда является соответствующим смежным подсписком списка первого операнда. (Для определения равенства подсписков выполняется сравнение соответствующих записей, как и для оператора равенства делегатов (§7.10.8).) Иначе результатом является значение левого операнда. В ходе этого процессе списки операндов не меняются. Если список второго операнда соответствует нескольким подспискам последовательных записей в списке первого операнда, то самый правый совпадающий подсписок последовательных записей удаляется. Если после удаления получается пустой список, то результатом является null. Пример:
delegate void D(int x);
class C
{
public static void M1(int i) { /* … */ }
public static void M2(int i) { /* … */ }
}
class Test
{
static void Main() {
D cd1 = new D(C.M1);
D cd2 = new D(C.M2);
D cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd1; // => M1 + M2 + M2
cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd1 + cd2; // => M2 + M1
cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd2 + cd2; // => M1 + M1
cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd2 + cd1; // => M1 + M2
cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1
cd3 -= cd1 + cd1; // => M1 + M2 + M2 + M1
}
}
Операторы сдвига
Операторы << и >> используются для выполнения операций побитового сдвига.
shift-expression:
additive-expression
shift-expression << additive-expression
shift-expression right-shift additive-expression
Если операнд выражения-сдвига имеет тип dynamic во время компиляции, то он динамически связан (§7.2.2). В этом случае тип времени компиляции выражения — dynamic, а разрешение, приведенное ниже, будет иметь место во время выполнения при использовании типа времени выполнения тех операндов, которые имеют тип dynamic во время компиляции.
Для операции вида x << count или x >> count, чтобы выбрать конкретную реализацию оператора, применяется разрешение перегрузки бинарного оператора (§7.3.4). Операнды преобразуются в типы параметров выбранного оператора, а тип результата является типом возвращаемого значения этого оператора.
При объявлении перегруженного оператора сдвига тип первого операнда всегда должен быть классом или структурой, в которой находится объявление оператора, а второй операнд должен иметь тип int.
Ниже перечислены стандартные операторы сдвига.
· Сдвиг влево:
int operator <<(int x, int count);
uint operator <<(uint x, int count);
long operator <<(long x, int count);
ulong operator <<(ulong x, int count);
Оператор << выполняет сдвиг значения x влево на определенное число битов и вычисляется, как указано ниже.
Старшие биты вне диапазона типа результирующего значения x отбрасываются, оставшиеся биты сдвигаются влево, а пустые позиции младших битов заполняются нулями.
· Сдвиг вправо
int operator >>(int x, int count);
uint operator >>(uint x, int count);
long operator >>(long x, int count);
ulong operator >>(ulong x, int count);
Оператор >> выполняет сдвиг значения x вправо на определенное число битов и вычисляется, как указано ниже.
Если x имеет тип int или long, то младшие биты x отбрасываются, оставшиеся биты сдвигаются вправо, а пустые позиции старших битов заполняются нулями, если x является неотрицательным числом, и заполняются единицами, если x является отрицательным числом.
Если x имеет тип uint или ulong, младшие биты x отбрасываются, оставшиеся биты сдвигаются вправо, а пустые позиции старших битов заполняются нулями.
Для стандартных операторов число сдвигаемых битов вычисляется следующим образом.
· Если x имеет тип int или uint, размер сдвига задается пятью младшими битами значения count. Другими словами, величина сдвига вычисляется от значения число count & 0x1F.
· Если x имеет тип long или ulong, размер сдвига задается шестью младшими битами значения count. Другими словами, величина сдвига вычисляется от значения число count & 0x3F.
Если результирующая величина сдвига равна нулю, то операторы сдвига просто возвращают значение x.
Операции сдвига никогда не вызывают переполнения и дают одинаковые результаты в контекстах checked и unchecked.
Если левый операнд оператора >> имеет целый тип со знаком, оператор выполняет арифметический сдвиг вправо, когда значение самого важного бита (бита знака) операнда распространяется на пустые позиции старших битов. Если левый операнд оператора >> имеет целый тип без знака, оператор выполняет логический сдвиг вправо, когда пустые позиции старших битов всегда заполняются нулями. Для выполнения обратной операции выведенной из типа операнда можно использовать явное приведение типов. Например, если x является переменной типа int, то операция unchecked((int)((uint)x >> y)) выполняет логический сдвиг x вправо.