Menu

Операторы в C#: арифметика, остаток от деления, логика, биты и приоритет

Операторы C# и их точное поведение: целочисленное деление, остаток от деления с отрицательными числами, инкремент и составное присваивание, сравнение, логика с коротким замыканием, побитовые операторы, операторы null и таблица приоритетов.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Оператор принимает один, два или три операнда и выдаёт значение: a + b, !done, x ?? fallback. Операторы C# близки к операторам C, Java и JavaScript, но некоторые из них ведут себя неожиданно для новичков, прежде всего целочисленное деление и знак результата %.

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

+, -, *, / и % работают с любым числовым типом. Тип результата следует за операндами: два значения int дают int, int и double дают double.

Вывод:

22
12
85
3
2
3.4
3.4
3
3.4

Целочисленное деление отбрасывает дробную часть в сторону нуля. 17 / 5 равно 3, а -17 / 5 равно -3. Это причина очень частой ошибки: int percent = done / total * 100; равно 0 всякий раз, когда done < total, потому что done / total уже равно 0. Сначала умножайте (done * 100 / total) или делите как double.

Деление целого числа или decimal на ноль выбрасывает DivideByZeroException. Деление double на ноль исключения не выбрасывает; получается бесконечность или NaN для 0.0 / 0.0:

Вывод:

True
True
False
DivideByZeroException

Остаток от деления и отрицательные числа

% возвращает остаток от деления, и его результат получает знак левого операнда. Это отличается от Python и от математического «mod», которые при положительном делителе всегда неотрицательны:

Вывод:

1
-1
1
2
even
62 min 5 s
Thu
1.5

Используйте вспомогательный метод Mod всегда, когда левая часть может быть отрицательной, а результат служит индексом: days[(today + offset) % 7] выбросит IndexOutOfRangeException при отрицательной сумме. У проверки на нечётность та же ловушка: n % 2 == 1 равно false для отрицательных нечётных чисел; проверяйте n % 2 != 0.

Присваивание, составное присваивание и инкремент

= присваивает. Составные формы +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= объединяют операцию с присваиванием. ++ и -- прибавляют или вычитают 1 и бывают в префиксной и постфиксной формах, которые различаются возвращаемым значением:

Вывод:

14
start, step
a=5 b=7 i=7
4

Случай с byte показывает особенность: level = level + 10; не компилируется, потому что byte + int даёт int, а level += 10; компилируется, потому что составное присваивание включает неявное приведение обратно к типу переменной. Это приведение при переполнении переходит через границу (250 + 10 превращается в 4).

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

Операторы сравнения (==, !=, <, >, <=, >=) возвращают bool. Для строк == сравнивает текст, а не ссылки. Логические операторы: ! (не), && (и), || (или) и ^ (исключающее или).

Вывод:

True
no active user
with ||:
  evaluated A
with |:
  evaluated A
  evaluated B
True

&& и || работают по короткой схеме: правый операнд выполняется, только если он может изменить результат. Именно поэтому user != null && user.IsActive безопасно. Одиночные & и | для логических значений всегда вычисляют обе части, а в условии это редко то, что нужно.

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

Для целочисленных типов &, |, ^ и ~ работают с отдельными битами, а << и >> сдвигают их. Их используют для флагов, масок, хеширования и низкоуровневых протоколов:

Вывод:

3
True
False
101
1024
125
5
-1

Для именованных наборов флагов перечисление с атрибутом [Flags] понятнее целочисленных констант, а HasFlag читается лучше маски. C# 11 добавил >>>, беззнаковый сдвиг вправо, который заполняет освободившиеся биты нулями даже для отрицательных чисел.

Операторы для null

Для работы со значениями, которые могут быть null, есть три оператора:

Вывод:

Guest

0
5

a ?? b возвращает a, если оно не null, иначе b. a?.Member возвращает null вместо исключения, когда a равно null. C# 8 добавил a ??= b, который присваивает b переменной a только тогда, когда a равно null. Подробно они разобраны в разделе типы, допускающие null.

Приоритет операторов

Когда в выражении смешаны операторы, строки выше связывают сильнее. Операторы одной строки группируются слева направо, кроме присваивания, ?? и ?:, которые группируются справа налево. (Сами операнды всегда вычисляются слева направо.)

КатегорияОператоры
Первичныеx.y, x?.y, f(x), a[i], x++, x--, new, typeof, nameof
Унарные+x, -x, !x, ~x, ++x, --x, (T)x, await
Мультипликативные*, /, %
Аддитивные+, -
Сдвиг<<, >>, >>>
Отношения и проверка типа<, >, <=, >=, is, as
Равенство==, !=
Побитовое И&
Побитовое исключающее ИЛИ^
Побитовое ИЛИ|
Условное И&&
Условное ИЛИ||
Объединение с null??
Условный операторc ? a : b
Присваивание и лямбда=, +=, -=, ??=, => и другие составные формы

Два следствия, которые стоит знать: a + b * c сначала умножает, а x & mask == 0 сначала сравнивает (== связывает сильнее, чем &), поэтому означает x & (mask == 0) и не компилируется для целых чисел. Пишите (x & mask) == 0. Скобки ничего не стоят и делают намерение очевидным.

Перегрузка операторов

Ваши собственные типы могут определять, что означают операторы. Именно так decimal, DateTime и TimeSpan поддерживают + и <:

Вывод:

33.49
True

Операторы должны быть public static, а некоторые обязательно определяются парами: определение < требует >, а == требует !=. Если определён ==, компилятор также выдаёт предупреждение, пока вы не переопределите Equals и GetHashCode согласованно. Перегружайте операторы только там, где их смысл очевиден, например для чисел, векторов и денег.

Часто задаваемые вопросы

Что делает % в C#?

% это оператор остатка от деления: 17 % 5 равно 2. Результат имеет знак левого операнда, поэтому -7 % 3 равно -1, а не 2, как в Python. Чтобы результат всегда был неотрицательным, как нужно при циклическом переборе индекса, пишите ((a % n) + n) % n. Оператор работает и с double и decimal: 7.5 % 2 равно 1.5.

Почему 7 / 2 равно 3 в C#?

Когда оба операнда целые, / выполняет целочисленное деление и отбрасывает дробную часть в сторону нуля. Сделайте один из операндов числом с плавающей точкой или decimal, чтобы получить дробный результат: 7 / 2.0 равно 3.5, (double)a / b преобразует перед делением, а 7m / 2 даёт 3.5 типа decimal.

Чем && отличается от & в C#?

&& это условное И: если левая часть равна false, правая не вычисляется. & для логических значений всегда вычисляет обе части, а для целых чисел это побитовое И. Используйте && и || в условиях, особенно в проверках вроде user != null && user.IsActive, которая с & выбросила бы NullReferenceException.

Чем i++ отличается от ++i в C#?

Оба прибавляют 1 к i. Выражение i++ возвращает старое значение, ++i новое. В отдельной строке (i++;) разницы нет. Она важна только тогда, когда результат используется, как в int a = i++;, поэтому многие руководства по стилю держат инкременты в отдельной строке.

Какой приоритет операторов в C#?

От высшего к низшему: первичные (x.y, f(x), a[i], x++), унарные (!, -, ++x, приведения), мультипликативные (* / %), аддитивные (+ -), сдвиги, отношения (< > <= >= is as), равенство (== !=), &, ^, |, &&, ||, ??, условный ?: и в конце присваивание. Если сомневаетесь, добавьте скобки.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ