Dovrei evitare di usare unsigned int in C #?

20

Recentemente ho pensato all'uso di interi senza segno in C # (e suppongo che un argomento simile possa essere detto su altri "linguaggi di alto livello")

Quando ho bisogno di un intero di solito non sono di fronte al dilemma della dimensione di un intero, un esempio sarebbe una proprietà di età di una classe Person (ma la domanda non è limitata alle proprietà). Con questo in mente c'è, per quanto posso vedere, solo un vantaggio dell'uso di un intero senza segno ("uint") su un intero con segno ("int") - leggibilità. Se desidero esprimere l'idea che un'età non può che essere positiva, posso ottenere ciò impostando il tipo di età su uint.

D'altro canto, i calcoli su interi senza segno possono portare a errori di ogni tipo e rende difficile eseguire operazioni come sottrarre due fasce di età. (Ho letto questo è uno dei motivi per cui Java ha omesso gli interi senza segno)

Nel caso di C # posso anche pensare che una clausola di guardia sul setter sarebbe una soluzione che dà il meglio di due mondi, ma, questo non sarebbe applicabile quando io per esempio, un'età sarebbe passata ad alcuni metodo. Una soluzione alternativa sarebbe definire una classe chiamata Age e avere l'età della proprietà essere l'unica cosa lì, ma questo modello mi farebbe creare molte classi e sarebbe fonte di confusione (altri sviluppatori non saprebbero quando un oggetto è solo un wrapper e quando è qualcosa di più sofisticato).

Quali sono alcune best practice generali relative a questo problema? Come dovrei affrontare questo tipo di scenario?

    
posta Belgi 06.01.2016 - 18:33
fonte

3 risposte

21

I progettisti di .NET Framework hanno scelto un intero con segno a 32 bit come "numero generale" per diversi motivi:

  1. È in grado di gestire numeri negativi, in particolare -1 (utilizzato dal Framework per indicare una condizione di errore, motivo per cui viene utilizzato un int firmato ovunque è richiesta l'indicizzazione, anche se i numeri negativi non sono significativi in un contesto di indicizzazione). / li>
  2. È abbastanza grande per servire la maggior parte degli scopi, pur essendo abbastanza piccolo da poter essere utilizzato economicamente quasi ovunque.

La ragione per usare unsigned uns è la leggibilità non ; sta avendo la capacità di ottenere i calcoli che fornisce solo un int unsigned.

Le clausole di guardia, la convalida e le precondizioni contrattuali sono modi perfettamente accettabili per assicurare intervalli numerici validi. Raramente un intervallo numerico del mondo reale corrisponde esattamente a un numero compreso tra zero e 2 32 -1 (o qualunque sia l'intervallo numerico nativo del tipo numerico scelto), quindi utilizzando uint a limitare il tuo contratto di interfaccia a numeri positivi è un po 'oltre il punto.

    
risposta data 06.01.2016 - 18:47
fonte
7

In genere, dovresti sempre utilizzare il tipo di dati più specifico per i tuoi dati.

Se, ad esempio, si utilizza Entity Framework per estrarre dati da un database, EF utilizzerà automaticamente il tipo di dati più vicino a quello utilizzato nel database.

Ci sono due problemi con questo in C #.
Innanzitutto, la maggior parte degli sviluppatori C # utilizza solo int , per rappresentare numeri interi (a meno che non vi sia un motivo per utilizzare long ). Ciò significa che altri sviluppatori non penseranno di verificare il tipo di dati, in modo da ottenere gli errori di overflow menzionati sopra. Il secondo problema, più critico, è / era che .NET operatori aritmetici originali supportava solo int , uint , long , ulong , float , double e decimal *. Questo è ancora il caso oggi (vedere la sezione 7.8.4 in Specifiche linguistiche C # 5.0 ). Puoi testarlo tu stesso usando il seguente codice:

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;

Il risultato del nostro byte - byte è un int ( System.Int32 ).

Questi due problemi hanno dato origine alla pratica "usa solo int per numeri interi" che è così comune.

Quindi per rispondere alla tua domanda, in C # è generalmente una buona idea attenersi a int a meno che:

  • Un generatore di codice automatizzato ha utilizzato un valore diverso (come Entity Framework).
  • Tutti gli altri sviluppatori del progetto sono consapevoli del fatto che stai utilizzando i tipi di dati meno comuni (includi un commento che indica che hai utilizzato il tipo di dati e perché).
  • I tipi di dati meno comuni sono già usati comunemente nel progetto.
  • Il programma richiede i vantaggi del tipo di dati meno comune (hai 100 milioni di questi devi tenere nella RAM, quindi la differenza tra byte e int o int e a long è critico, o le differenze aritmetiche di unsigned già menzionate).

Se hai bisogno di fare matematica sui dati, segui i tipi più comuni.
Ricorda, puoi trasmettere da un tipo all'altro. Questo può essere meno efficiente dal punto di vista della CPU, quindi probabilmente stai meglio con uno dei 7 tipi comuni, ma è un'opzione, se necessario.

Enumerazioni ( enum ) è una delle mie eccezioni personali alle linee guida di cui sopra. Se ho solo poche opzioni, farò specificare l'enum come byte o breve . Se ho bisogno dell'ultimo bit in un enumerato, specificherò il tipo in uint , quindi posso usare hex per impostare il valore per il flag.

Se utilizzi una proprietà con codice che limita i valori, assicurati di spiegare nel tag di riepilogo quali restrizioni esistono e perché.

* Alias di C # vengono utilizzati al posto di nomi .NET come System.Int32 poiché si tratta di una domanda in C #.

Nota: c'era un blog o un articolo degli sviluppatori .NET (che non trovo), che indicava il numero limitato di funzioni aritmetiche e alcuni motivi per cui non se ne preoccupavano. Come ricordo, hanno indicato che non avevano intenzione di aggiungere supporto per gli altri tipi di dati.

Nota: Java non supporta i tipi di dati non firmati e in precedenza non aveva supporto per numeri interi a 8 o 16 bit. Dal momento che molti sviluppatori di C # provenivano da uno sfondo Java o avevano bisogno di lavorare in entrambe le lingue, le limitazioni di una lingua venivano talvolta imposte artificialmente dall'altra.

    
risposta data 07.01.2016 - 03:54
fonte
5

Devi principalmente conoscere due cose: i dati che stai rappresentando e tutti i passaggi intermedi nei tuoi calcoli.

Ha certamente senso avere un'età pari a unsigned int , perché di solito non consideriamo le età negative. Ma poi dici di sottrarre un'età a un'altra. Se ci limitiamo a sottrarre ciecamente un intero da un altro, allora è sicuramente possibile finire con un numero negativo, anche se in precedenza concordavamo che le età negative non avevano senso. Quindi in questo caso vorresti eseguire il calcolo con un intero con segno.

Riguardo al fatto che i valori non firmati siano negativi o no, direi che è un'enorme generalizzazione affermare che i valori non firmati sono negativi. Java non ha valori non firmati, come hai detto, e mi infastidisce costantemente. Un byte può avere un valore compreso tra 0-255 o 0x00-0xFF. Ma se vuoi istanziare un byte più grande di 127 (0x7F), devi scriverlo come numero negativo o lanciare un intero su un byte. Finisci con il codice che assomiglia a questo:

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

Quanto sopra mi infastidisce senza fine. Non mi è permesso che un byte abbia un valore di 197, anche se questo è un valore perfettamente valido per la maggior parte delle persone sane che si occupano di byte. Posso fondere il numero intero o posso trovare il valore negativo (197 == -59 in questo caso). Considera anche questo:

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

Quindi, come puoi vedere, aggiungendo due byte con valori validi e finendo con un byte con un valore valido, si finisce per cambiare il segno. Non solo, ma non è immediatamente ovvio che 70 + 80 == -106. Tecnicamente si tratta di un overflow, ma nella mia mente (come essere umano) un byte non dovrebbe traboccare per valori sotto 0xFF. Quando eseguo il bit aritmetico su carta, non considero l'ottavo bit un bit di segno.

Lavoro con molti interi a livello di bit, e avendo tutto firmato solitamente rende tutto meno intuitivo e più difficile da gestire, perché devi ricordare che spostando a destra un numero negativo ti viene dato un nuovo 1 s nel tuo numero Considerando che lo spostamento verso destra di un intero senza segno non lo fa mai. Ad esempio:

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;

Aggiunge solo ulteriori passaggi che ritengo non dovrebbero essere necessari.

Mentre ho usato byte sopra, lo stesso vale per gli interi 32-bit e 64-bit. Non avere unsigned è paralizzante e mi sconvolge che ci siano linguaggi di alto livello come Java che non li consentono affatto. Ma per la maggior parte delle persone questo non è un problema, perché molti programmatori non si occupano dell'aritmetica a livello di bit.

Alla fine, è utile usare numeri interi non firmati se li stai pensando come bit, ed è utile usare interi con segno quando li stai pensando come numeri.

    
risposta data 06.01.2016 - 19:18
fonte

Leggi altre domande sui tag