J'ai récemment pensé à l'utilisation d'entiers non signés en C # (et je suppose que l'on peut dire un argument similaire à propos d'autres "langages de haut niveau")
Lorsque j'ai besoin d'un entier, je ne suis normalement pas confronté au dilemme de la taille d'un entier, un exemple serait une propriété d'âge d'une classe Person (mais la question ne se limite pas aux propriétés). Dans cet esprit, il n'y a, à ma connaissance, qu'un seul avantage à utiliser un entier non signé ("uint") par rapport à un entier signé ("int") - la lisibilité. Si je souhaite exprimer l'idée qu'un âge ne peut être que positif, je peux y parvenir en définissant le type d'âge sur uint.
En revanche, les calculs sur des entiers non signés peuvent entraîner des erreurs de toutes sortes et il est difficile d'effectuer des opérations telles que la soustraction de deux âges. (J'ai lu que c'est l'une des raisons pour lesquelles Java a omis des entiers non signés)
Dans le cas de C #, je peux également penser qu'une clause de garde sur le setter serait une solution qui donne le meilleur des deux mondes, mais, cela ne serait pas applicable lorsque je par exemple, un âge serait passé à une certaine méthode. Une solution de contournement consisterait à définir une classe appelée Age et à ce que la propriété age soit la seule chose, mais ce modèle me ferait créer de nombreuses classes et serait une source de confusion (les autres développeurs ne sauraient pas quand un objet n'est qu'un wrapper et quand c'est quelque chose de plus sofisticadé).
Quelles sont les meilleures pratiques générales concernant ce problème? Comment dois-je gérer ce type de scénario?
la source
Réponses:
Les concepteurs du .NET Framework ont choisi un entier signé 32 bits comme "numéro à usage général" pour plusieurs raisons:
La raison d'utiliser des entiers non signés n'est pas la lisibilité; il a la capacité d'obtenir les calculs que seul un int non signé fournit.
Les clauses de garde, la validation et les conditions préalables au contrat sont des moyens parfaitement acceptables d'assurer des plages numériques valides. Une plage numérique réelle correspond rarement à un nombre compris entre zéro et 2 32 -1 (ou quelle que soit la plage numérique native du type numérique que vous avez choisi), donc utiliser un
uint
pour contraindre votre contrat d'interface à des nombres positifs est une sorte de sans rapport.la source
for (uint j=some_size-1; j >= 0; --j)
- whoops ( Je ne sais pas si c'est un problème en C #)! J'ai trouvé ce problème dans le code avant qui essayait d'utiliser autant que possible un entier non signé du côté C - et nous avons fini par le changer pour le favoriserint
plus tard, et nos vies étaient beaucoup plus faciles avec moins d'avertissements du compilateur.int
plupart du temps parce que c'est la convention établie, et c'est ce que la plupart des gens s'attendent à voir utilisé régulièrement. Utilisez-leuint
lorsque vous avez besoin des capacités spéciales d'unuint
. N'oubliez pas que les concepteurs de Framework ont décidé de suivre cette convention de manière approfondie, vous ne pouvez donc même pas l'utiliseruint
dans de nombreux contextes de Framework (il n'est pas compatible avec le type).En règle générale, vous devez toujours utiliser le type de données le plus spécifique possible pour vos données.
Si, par exemple, vous utilisez Entity Framework pour extraire des données d'une base de données, EF utilisera automatiquement le type de données le plus proche de celui utilisé dans la base de données.
Il y a deux problèmes avec cela en C #.
Tout d'abord, la plupart des développeurs C # utilisent uniquement
int
, pour représenter des nombres entiers (sauf s'il y a une raison d'utiliserlong
). Cela signifie que les autres développeurs ne penseront pas à vérifier le type de données, ils obtiendront donc les erreurs de débordement mentionnées ci-dessus. La deuxième et question plus critique, est / était que de .NET opérateurs arithmétiques d' origine uniquement pris en chargeint
,uint
,long
,ulong
,float
, double, etdecimal
*. C'est toujours le cas aujourd'hui (voir la section 7.8.4 dans les spécifications du langage C # 5.0 ). Vous pouvez le tester vous-même à l'aide du code suivant:Le résultat de notre
byte
-byte
est unint
(System.Int32
).Ces deux problèmes ont donné lieu à la pratique de "n'utiliser que des nombres entiers" qui est si courante.
Donc, pour répondre à votre question, en C #, c'est généralement une bonne idée de s'en tenir à
int
moins que:byte
et unint
ou unint
et unlong
est critique, ou les différences arithmétiques des éléments non signés déjà mentionnés).Si vous devez faire des calculs sur les données, respectez les types courants.
N'oubliez pas que vous pouvez effectuer un cast d'un type à un autre. Cela peut être moins efficace du point de vue du processeur, vous êtes donc probablement mieux avec l'un des 7 types courants, mais c'est une option si nécessaire.
Enumerations (
enum
) est l'une de mes exceptions personnelles aux directives ci-dessus. Si je n'ai que quelques options, je spécifierai que l'énumération est un octet ou un court. Si j'ai besoin de ce dernier bit dans une énumération signalée, je spécifierai le type à utiliseruint
afin de pouvoir utiliser hex pour définir la valeur du drapeau.Si vous utilisez une propriété avec un code de restriction de valeur, assurez-vous d'expliquer dans la balise récapitulative quelles sont les restrictions et pourquoi.
* Les alias C # sont utilisés à la place des noms .NET,
System.Int32
car il s'agit d'une question C #.Remarque: il y avait un blog ou un article des développeurs .NET (que je ne peux pas trouver), qui soulignait le nombre limité de fonctions arithmétiques et certaines raisons pour lesquelles ils ne s'en préoccupaient pas. Si je me souviens bien, ils ont indiqué qu'ils n'avaient pas l'intention d'ajouter la prise en charge des autres types de données.
Remarque: Java ne prend pas en charge les types de données non signés et ne prenait auparavant pas en charge les nombres entiers 8 ou 16 bits. Étant donné que de nombreux développeurs C # venaient d'un arrière-plan Java ou devaient travailler dans les deux langues, les limitations d'une langue étaient parfois imposées artificiellement à l'autre.
la source
Vous devez principalement être conscient de deux choses: les données que vous représentez et toutes les étapes intermédiaires de vos calculs.
Il est certainement logique d'avoir l'âge
unsigned int
, car nous ne considérons généralement pas les âges négatifs. Mais vous mentionnez ensuite la soustraction d'un âge à un autre. Si nous soustrayons aveuglément un entier d'un autre, il est certainement possible de se retrouver avec un nombre négatif, même si nous avons convenu précédemment que les âges négatifs n'ont pas de sens. Donc, dans ce cas, vous voudriez que votre calcul soit fait avec un entier signé.Quant à savoir si les valeurs non signées sont mauvaises ou non, je dirais que c'est une énorme généralisation de dire que les valeurs non signées sont mauvaises. Java n'a pas de valeurs non signées, comme vous l'avez mentionné, et cela m'agace constamment. A
byte
peut avoir une valeur comprise entre 0-255 ou 0x00-0xFF. Mais si vous souhaitez instancier un octet supérieur à 127 (0x7F), vous devez soit l'écrire sous la forme d'un nombre négatif, soit convertir un entier en octet. Vous vous retrouvez avec un code qui ressemble à ceci:Ce qui précède m'ennuie sans fin. Je ne suis pas autorisé à avoir un octet ayant une valeur de 197, même si c'est une valeur parfaitement valide pour la plupart des gens sensés traitant des octets. Je peux convertir l'entier ou trouver la valeur négative (197 == -59 dans ce cas). Considérez également ceci:
Donc, comme vous pouvez le voir, l'ajout de deux octets avec des valeurs valides et la fin avec un octet avec une valeur valide finissent par changer le signe. Non seulement cela, mais il n'est pas immédiatement évident que 70 + 80 == -106. Techniquement, c'est un débordement, mais dans mon esprit (en tant qu'être humain), un octet ne devrait pas déborder pour les valeurs sous 0xFF. Quand je fais de l'arithmétique sur papier, je ne considère pas que le 8e bit soit un bit de signe.
Je travaille avec beaucoup d'entiers au niveau du bit, et le fait que tout soit signé rend généralement tout moins intuitif et plus difficile à gérer, car vous devez vous rappeler que le décalage à droite d'un nombre négatif vous donne de nouveaux
1
s dans votre nombre. Alors que déplacer vers la droite un entier non signé ne fait jamais cela. Par exemple:Cela ajoute simplement des étapes supplémentaires qui, selon moi, ne devraient pas être nécessaires.
Alors que j'ai utilisé
byte
ci-dessus, la même chose s'applique aux entiers 32 bits et 64 bits. Ne pas avoirunsigned
est paralysant et cela me choque qu'il existe des langages de haut niveau comme Java qui ne les autorisent pas du tout. Mais pour la plupart des gens, ce n'est pas un problème, car de nombreux programmeurs ne traitent pas avec l'arithmétique au niveau du bit.En fin de compte, il est utile d'utiliser des entiers non signés si vous les considérez comme des bits, et il est utile d'utiliser des entiers signés lorsque vous les considérez comme des nombres.
la source