Игра битами: что означает предупреждение CS0675?

От высших материй стиля передачи продолжений мы возвращаемся к мирским проблемам игры с конкретными битами.

 int i = SomeBagOfBits();
ulong u = SomeOtherBagOfBits();
ulong result = u | i; // объединяем их

Упс, получаем ошибку. “Оператор | не может применяться к операндам с типами int и ulong . Битовый оператор ИЛИ определен для типов int, uint, long и ulong, но не определен для типов int и ulong. Вы не можете использовать версию для типа int, поскольку ulong может превосходить размер int, и вы не можете использовать версию для типа ulong, поскольку int может содержать отрицательные значения.

Но я хочу, чтобы компилятор все же выполнил мой приказ!

 ulong result = u | (ulong) i;

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

Ух ты, теперь мы получаем предупреждение! CS0675: битовый оператор ИЛИ используется со знаковым операндом; подумайте о приведении операнда к ближайшему беззнаковому целому”.

Меня часто спрашивают, что означает это предупреждение. Проблема заключается в том, что при преобразовании из int в ulong происходит расширение знака. Давайте приведем более конкретный пример:

 int i = -17973521; // В шестнадцатеричном виде это FEEDBEEF
ulong u = 0x0123456700000000;
ulong result = u | (ulong)i;
Console.WriteLine(result.ToString("x"));

Какой вы ожидаете результат? Большинство людей ожидают, что результатом будет 1234567FEEDBEEF. Однако это не так. Результатом будет FFFFFFFFFEEDBEEF. Почему? Потому что при конвертации из int в ulong, вначале int преобразуется к long, чтобы информация о знаке не терялась. В шестнадцатеричном виде значение -17973521 типа long равняется FFFFFFFFFEEDBEEF. Именно это значение типа long преобразуется к типу ulong, которое затем обычным образом используется в операторе ИЛИ но приводит к неожиданному результату.

Компилятор предупреждает вас о том, что такое поведение почти всегда неверное. Для устранения предупреждения вначале вам следует решить, нужно ли вам расширение знака или нет. Если вы не хотите этого, тогда следуйте совету, который дается в предупреждении. Не просто ж так, текст предупреждения гласит «подумайте о приведении операнда к ближайшему беззнаковому целому»!

 ulong result = u | (uint)i;

Это скажет компилятору «вначале преобразовать int в uint, а затем преобразовать uint в ulong». Поскольку в этом случае все вычисления будут выполняться над беззнаковыми типами, то никакого расширения знака не будет.

Если же по какой-то причине вам нужна семантика расширения знака, то скажите об этом компилятору явно:

 ulong result = u | (ulong)(long)i;

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

Однако лучше вообще не попадать в эту хитрую ситуацию. Во-первых, если вы можете избежать манипулирования битами, избегайте его. Сегодня я очень редко сталкиваюсь с необходимостью манипулирования битами над встроенными «примитивными» типами. Используйте перечисление с атрибутом Flags, если вы хотите представить набор битовых флагов в компактном виде. Во-вторых, если вы вынуждены использовать примитивные типы, не используйте битовые операции со знаковыми типами; это практически всегда неправильно. В-третьих, старайтесь избегать битовых операций с операндами различных размеров; это тоже должно быть для вас тревожным сигналом.

Оригинал статьи