Чи слід уникати використання неподписаного int в C #?


23

Нещодавно я думав про використання непідписаних цілих чисел у C # (і, мабуть, подібний аргумент можна сказати і про інші "мови високого рівня")

Коли мені потрібно ціле число, я зазвичай не стикаюся з дилемою розміру цілого числа, прикладом може бути вікова властивість класу Person (але питання не обмежується властивостями). Зважаючи на це, наскільки я бачу, є лише одна перевага використання непідписаного цілого числа ("uint") над підписаним цілим числом ("int") - читабельність. Якщо я хочу висловити думку про те, що вік може бути лише позитивним, я можу досягти цього, встановивши віковий тип, щоб не змінити.

З іншого боку, обчислення непідписаних цілих чисел можуть призвести до помилок різного роду, і це ускладнює виконання таких операцій, як віднімання двох віків. (Я читав, що це одна з причин, коли Java опустила непідписані цілі числа)

У випадку з C # я також можу подумати, що охоронний пункт про сеттер буде рішенням, яке дає найкраще з двох світів, але це не застосовується, коли я, наприклад, вік переходить на якийсь метод. Приблизним рішенням було б визначити клас під назвою Age, а вік властивості - це єдине, але ця модель дозволила б Мені створити багато класів і буде джерелом плутанини (інші розробники не знають, коли об’єкт є лише обгорткою а коли це щось більш софітистичне).

Які загальні найкращі практики щодо цього питання? Як мені впоратися з таким типом сценарію?



1
Додатково неподписаний int не відповідає сумісності CLS, а це означає, що ви не можете викликати API, які використовують їх з інших мов .NET.
Натан Купер

2
@NathanCooper: ... «не може викликати API - інтерфейси , які використовують їх з деяких інших мов». Метадані для них стандартизовані, тому всі мови .NET, які підтримують типи, які не підписуються, будуть взаємодіяти чудово.
Ben Voigt

5
Щоб вирішити ваш конкретний приклад, у мене не було б властивості під назвою Age. У мене буде властивість під назвою Birthday або CreationTime або будь-що інше, і я обчислював би вік від цього.
Ерік Ліпперт

2
"... але цей зразок змусив би Мене створити багато класів і був би джерелом плутанини" насправді це правильно робити. Просто шукайте сумнозвісну анти-модель " Примітивні одержимості" .
Сонго

Відповіді:


24

Конструктори .NET Framework вибрали ціле число з 32 бітами, підписаним як їх "загальне число" з кількох причин:

  1. Він може обробляти негативні числа, особливо -1 (який Framework використовує для вказівки на стан помилки; саме тому підписаний int використовується скрізь, де потрібна індексація, хоча негативні числа не мають значення в контексті індексації).
  2. Він досить великий, щоб служити більшості цілей, в той час як досить малий, щоб використовувати його економно майже в будь-якому місці.

Причиною використання непідписаних ints є не читабельність; він має можливість отримати математику, яку надає лише неподписаний int.

Застереження, умови перевірки та умови контракту є цілком прийнятними способами страхування дійсних числових діапазонів. Рідко чисельний діапазон у реальному світі відповідає точно такому числу між нулем і 2 32 -1 (або будь-який власний числовий діапазон чисельного типу, який ви вибрали), тож використання uintобмеження контракту на інтерфейс на додатні числа є своєрідним поруч із пунктом.


2
Гарна відповідь! Також там можуть бути деякі випадки , коли непідписаних INT може фактично ненавмисно виробляти більше помилок (хоча , ймовірно , ті одразу помітили, але трохи заплутаним) - уявіть , зациклення в зворотному з непідписаних Int лічильник , тому що деякий розмір є цілим числом: for (uint j=some_size-1; j >= 0; --j)- вигуки ( не впевнений, чи це проблема в C #)! Я знайшов цю проблему в коді, перед яким намагався якомога більше використати неподписаний int на стороні C, і ми в кінцевому підсумку змінили її на користь intпізніше, і наше життя було набагато простішим і з меншими попередженнями компілятора.

14
"Рідко чисельний діапазон реального світу відповідає числу між нулем і 2 ^ 32-1." На мій досвід, якщо вам знадобиться число, більше 2 ^ 31, вам, швидше за все, знадобляться також числа, більші за 2 ^ 32, тож ви можете просто перейти до (підписаного) int64 на ця точка.
Мейсон Уілер

3
@Panzercrisis: Це трохи важко. Напевно, було б точніше сказати "Використовуйте intбільшу частину часу, тому що це встановлена ​​конвенція, і це те, що більшість людей очікують побачити використане в рутині. Використовуйте, uintколи вам потрібні спеціальні можливості" uint. Пам'ятайте, що розробники Framework вирішили дотримуватися цієї конвенції широко, тому ви навіть не можете використовуватись uintу багатьох контекстах Framework (це не сумісно з типом).
Роберт Харві

2
@Panzercrisis Це може бути занадто сильним фразуванням; але я не впевнений, чи використовував я коли-небудь неподписані типи в C #, за винятком випадків, коли я викликав win32 apis (де умовно, що константи / прапори / тощо не підписані).
Дан Нілі

4
Це дійсно досить рідко. Єдиний раз, коли я коли-небудь використовую непідписані вставки, це сценарії біт-подвійності.
Роберт Харві

8

Як правило, ви завжди повинні використовувати для своїх даних найбільш конкретний тип даних.

Якщо, наприклад, ви використовуєте Entity Framework для витягу даних з бази даних, EF автоматично використовуватиме тип даних, найбільш близький до типу, який використовується в базі даних.

З цим у C # є дві проблеми.
По-перше, більшість розробників C # використовують лише intдля представлення цілих чисел (якщо немає причин для використання long). Це означає, що інші розробники не думають перевіряти тип даних, тому вони отримають помилки переповнення, згадані вище. Під - друге, і більш важливим питанням, є / в тому , що .NET в вихідні арифметичні оператори підтримуються тільки int, uint, long, ulong, float, подвійний, і decimal*. Це все ще має місце сьогодні (див. Розділ 7.8.4 в мові C # 5.0 ). Ви можете перевірити це самостійно, використовуючи наступний код:

byte a, b;
a = 1;
b = 2;
var c = a - b;      //In visual studio, hover over "var" and the tip will indicate the data type, or you can get the value from cName below.
string cName = c.GetType().Namespace + '.' + c.GetType().Name;

Результат нашого byte- byteце int( System.Int32).

Ці два питання породили практику "використання лише цілого числа", яка є такою поширеною.

Тож, щоб відповісти на ваше запитання, зазвичай на C # зазвичай дотримуватися, intякщо:

  • Автоматизований генератор коду використовував інше значення (наприклад, Entity Framework).
  • Всі інші розробники проекту усвідомлюють, що ви використовуєте менш поширені типи даних (включте коментар із зазначенням того, що ви використовували тип даних та чому).
  • Менш поширені типи даних вже часто використовуються в проекті.
  • Програма вимагає переваг менш поширеного типу даних (у вас є 100 мільйонів, яких вам потрібно зберегти в оперативній пам'яті, тому різниця між a byteі an intабо inta longє критичною, або арифметичні відмінності неподписаних вже згаданих).

Якщо вам потрібно зробити математику за даними, дотримуйтесь загальних типів.
Пам'ятайте, що ви можете передавати один тип до іншого. Це може бути менш ефективно з точки зору процесора, тому вам, мабуть, краще з одним із 7 поширених типів, але це варіант, якщо це потрібно.

Перерахування ( enum) - одне з моїх особистих винятків із вищезазначених вказівок. Якщо у мене є лише кілька варіантів, я визначу, що перерахунок буде байтом або коротким. Якщо мені потрібен останній біт у переліченому переліченні, я вкажу тип, який повинен бути, uintщоб я міг використовувати шістнадцятковий для встановлення значення для прапора.

Якщо ви використовуєте властивість із кодом, що обмежує значення, обов’язково поясніть у тезі підсумків, які обмеження існують та чому.

* Псевдоніми C # використовуються замість імен .NET, як-от, System.Int32оскільки це питання C #.

Примітка. У розробниках .NET був блог або стаття (яку я не можу знайти), де вказувалося на обмежену кількість арифметичних функцій та деякі причини, чому вони не турбувалися про це. Як я пам’ятаю, вони вказали, що не планують додавати підтримку для інших типів даних.

Примітка: Java не підтримує неподписані типи даних і раніше не підтримувала 8 або 16 бітових цілих чисел. Оскільки багато розробників C # походять з Java або потрібно працювати на обох мовах, обмеження однієї мови іноді штучно накладаються на іншу.


Моє загальне правило: просто "використовувати int, якщо ви не можете".
PerryC

@PerryC Я вважаю, що це найпоширеніша умова. Суть моєї відповіді полягала в наданні більш повної конвенції, яка дозволяє використовувати мовні функції.
Trisped

6

В основному вам потрібно знати про дві речі: дані, які ви представляєте, і будь-які проміжні кроки у ваших розрахунках.

Звичайно, має сенс мати вік unsigned int, оскільки ми зазвичай не вважаємо негативними віками. Але потім ви згадуєте віднімання одного віку від іншого. Якщо ми просто сліпо віднімаємо одне ціле число від іншого, то це, безумовно, можна в кінцевому підсумку з від’ємним числом, навіть якщо ми раніше погоджувались, що від’ємні віки не мають сенсу. Тож у цьому випадку ви хочете, щоб ваш розрахунок робився з підписаним цілим числом.

Стосовно того, чи є неподписані значення поганими чи ні, я б сказав, що велике узагальнення можна сказати, що неподписані значення погані. Як ви сказали, у Java немає знаків без підпису, і це мене постійно дратує. А byteможе мати значення від 0-255 або 0x00-0xFF. Але якщо ви хочете створити байт, більший за 127 (0x7F), вам потрібно буде записати його як від'ємне число або закинути ціле число в байт. Ви закінчуєте код, який виглядає приблизно так:

byte a = 0x80; // Won't compile!
byte b = (byte) 0x80;
byte c = -128; // Equal to b

Вище сказане дратує мене без кінця. Мені не дозволено, щоб байт мав значення 197, хоча це цілком дійсне значення для більшості здорових людей, що мають справу з байтами. Я можу подати ціле число або можу знайти від’ємне значення (197 == -59 в цьому випадку). Також врахуйте це:

byte a = 70;
byte b = 80;
byte c = a + b; // c == -106

Отже, як ви бачите, додавання двох байтів із допустимими значеннями та закінчення байту з дійсним значенням призводить до зміни знаку. Мало того, але не відразу очевидно, що 70 + 80 == -106. Технічно це переповнення, але на мій погляд (як людина) байт не повинен переповнюватися для значень нижче 0xFF. Коли я роблю бітну арифметику на папері, я не вважаю, що 8-й біт є бітовим знаком.

Я працюю з безліччю цілих чисел на рівні бітів, і якщо все підписано, зазвичай все стає менш інтуїтивним і складніше мати справу, тому що ви повинні пам’ятати, що правильне зміщення від’ємного числа дає вам нові 1s у вашій кількості. Тоді як праворучне зміщення непідписаного цілого числа ніколи цього не робить. Наприклад:

signed byte b = 0b10000000;
b = b >> 1; // b == 0b1100 0000
b = b & 0x7F;// b == 0b0100 0000

unsigned byte b = 0b10000000;
b = b >> 1; // b == 0b0100 0000;

Це просто додає додаткові кроки, які, на мою думку, не потрібні.

Хоча я використовував byteвище, те саме стосується 32-бітних та 64-бітних цілих чисел. Не маючи unsignedв паралізуємо і жахає! , Що є мови високого рівня , такі як Java , які не дозволяють їм взагалі. Але для більшості людей це не проблема, оскільки багато програмістів не займаються арифметикою бітового рівня.

Зрештою, корисно використовувати непідписані цілі числа, якщо ви думаєте про них як біти, а корисно використовувати підписані цілі числа, коли ви думаєте про них як про числа.


7
Я поділяю ваше розчарування щодо мов без непідписаних цілісних типів (особливо для байтів), але я боюся, що це не пряма відповідь на поставлене тут питання. Можливо, ви можете додати висновок, який, я вважаю, міг би бути: "Використовуйте непідписані цілі числа, якщо ви думаєте про їх значення як біти та підписані цілі числа, якщо ви думаєте про них як числа".
5gon12eder

1
це те, що я сказав у коментарі вище. рада бачити, як хтось думає так само.
Роберт Брістоу-Джонсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.