C#でunsigned intを使用しないようにする必要がありますか?


23

最近、C#で符号なし整数を使用することを考えました(他の「高レベル言語」についても同様の議論が言えると思います)

整数が必要なとき、私は通常整数のサイズのジレンマに直面していないとき、例はPersonクラスの年齢プロパティです(ただし、質問はプロパティに限定されません)。それを念頭に置いて、私が見る限り、符号付き整数( "int")よりも符号なし整数( "uint")を使用する利点は1つだけです。それは読みやすさです。年齢が正の数にしかならないという考えを表現したい場合は、年齢タイプをuintに設定することでこれを達成できます。

一方、符号なし整数の計算では、あらゆる種類のエラーが発生する可能性があり、2つの年齢を減算するなどの操作を行うことが難しくなります。(私はこれがJavaが符号なし整数を省略した理由の1つだと読んだ)

C#の場合、セッターのガード句は2つの世界の最高のものを提供するソリューションになると考えることもできますが、たとえば、年齢が何らかの方法に渡される場合、これは適用できません。回避策は、Ageというクラスを定義し、プロパティageのみをそこに置くことですが、このパターンでは、Meに多くのクラスを作成させ、混乱の原因になります(他の開発者は、オブジェクトが単なるラッパーであるときそして、それがより洗練されたものである場合)。

この問題に関する一般的なベストプラクティスは何ですか?このタイプのシナリオにどのように対処すればよいですか?



1
さらに、unsigned intはCLSに準拠していないため、他の.NET言語からそれらを使用するAPIを呼び出すことはできません。
ネイサンクーパー

2
@NathanCooper:...「からそれらを使用するAPIを呼び出すことはできませんいくつかの他の言語」。それらのメタデータは標準化されているため、符号なしの型をサポートするすべての.NET言語は正常に相互運用できます。
ベンフォークト

5
あなたの特定の例を扱うために、そもそもAgeというプロパティはありません。BirthdayやCreationTimeなどのプロパティがあり、それから年齢を計算します。
エリックリッパー

2
「...しかし、このパターンは、私に多くのクラスを作成させ、混乱の原因になります」実際、それは正しいことです。悪名高いプリミティブオブセッションアンチパターンを検索するだけです。
ソンゴ

回答:


24

.NET Frameworkの設計者は、いくつかの理由により、32ビット符号付き整数を「汎用番号」として選択しました。

  1. 負の数、特に-1を処理できます(フレームワークはエラー状態を示すために使用します。これは、インデックス付けコンテキストで負の数が意味を持たない場合でも、インデックス付けが必要なすべての場所でsigned intが使用される理由です)。
  2. ほとんどの目的に十分な大きさですが、ほとんどどこでも経済的に使用できるほど小さいです。

符号なし整数を使用する理由は読みやすさではありません。unsigned intのみが提供する数学を取得する機能を備えています。

ガード条項、検証、および契約の前提条件は、有効な数値範囲を保証するための完全に受け入れられる方法です。ゼロから2 32 -1までの数値(または選択した数値型のネイティブ数値範囲)に正確に対応する実際の数値範囲はほとんどありません。そのためuint、インターフェイスコントラクトを正の数値に制限するにはポイントの横に。


2
いい答えだ!また、unsigned intが実際に誤ってより多くのエラーを生成する場合があります(おそらくすぐに発見されますが、少し混乱します)-いくつかのサイズが整数であるため、unsigned intカウンターで逆ループすることを想像してください:for (uint j=some_size-1; j >= 0; --j)-whoops(これがC#の問題かどうかわからない)!この問題は、C側でunsigned intを可能な限り使用しようとする前にコードで発見しました-そしてint、後でそれを好むように変更することになり、コンパイラの警告も少なくなり、私たちの生活はずっと楽になりました。

14
「ゼロから2 ^ 32-1の間の数値に対応する実際の数値範囲はほとんどありません。」私の経験では、2 ^ 31より大きい数値が必要な場合、2 ^ 32より大きい数値も必要になる可能性が非常に高いので、次の場所で(符号付き)int64に移動することもできます。その点。
メイソンウィーラー

3
@Panzercrisis:それは少し厳しいです。「intそれは確立された慣習であるため、ほとんどの時間を使用し、それはほとんどの人が日常的に使用されることを期待するものです。uintあなたが特別な機能を必要とするときに使用しますuint。」フレームワークの設計者はこの規約に従うことを決定したためuint、多くのフレームワークコンテキストで使用することさえできないことを覚えておいてください(型互換性はありません)。
ロバートハーベイ

2
@Panzercrisisそれは過度に強いフレージングかもしれません。しかし、Win32 APIを呼び出すときを除いて、C#で符号なしの型を使用したことがあるかどうかはわかりません(規則では、定数/フラグなどは符号なしです)。
ダン・ニーリー

4
それは確かに非常にまれです。私がこれまでに符号なし整数を使用するのは、ビットトゥイドリングシナリオのみです。
ロバートハーベイ

8

一般に、可能な限りデータに最も具体的なデータ型を常に使用する必要があります。

たとえば、Entity Frameworkを使用してデータベースからデータをプルしている場合、EFはデータベースで使用されているデータ型に最も近いデータ型を自動的に使用します。

C#にはこれに関する2つの問題があります。
まず、ほとんどのC#開発者はを使用してint、整数を表します(使用する理由がない限りlong)。これは、他の開発者がデータ型をチェックすることを考えないため、上記のオーバーフローエラーが発生することを意味します。第二、そしてより重要な問題は、/。NETのことだったされ、元の算術演算子がサポートされる唯一のintuintlongulongfloat、ダブル、およびdecimal*。これは今日でも当てはまります(C#5.0言語仕様のセクション7.8.4を参照)。次のコードを使用して、これを自分でテストできます。

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ですintSystem.Int32)。

これら2つの問題により、「整数には整数のみを使用する」という慣行が生まれました。

したがって、あなたの質問に答えるには、C#では、次のint場合を除いて固執することをお勧めします。

  • 自動化されたコードジェネレーターは、異なる値(Entity Frameworkなど)を使用しました。
  • プロジェクトの他のすべての開発者は、あまり一般的ではないデータ型を使用していることを認識しています(データ型を使用したこととその理由を指摘するコメントを含めます)。
  • あまり一般的ではないデータ型は、プロジェクトで既に一般的に使用されています。
  • プログラムには、あまり一般的でないデータ型の利点が必要です(RAMに保持する必要があるのは1億個あるため、a byteとan intまたはan intとaのlong差が重要であるか、すでに述べた符号なしの算術差です)。

データを計算する必要がある場合は、一般的な型に固執してください。
あるタイプから別のタイプにキャストできることを忘れないでください。これは、CPUの観点からすると効率が低下する可能性があるため、7つの一般的なタイプのいずれかを使用する方が適切ですが、必要な場合はオプションです。

列挙(enum)は、上記のガイドラインに対する私の個人的な例外の1つです。いくつかのオプションしかない場合、enumをbyteまたはshortに指定します。フラグ付き列挙の最後のビットが必要な場合は、uint16進数を使用してフラグの値を設定できるように、タイプを指定します。

値を制限するコードでプロパティを使用する場合は、概要タグでどのような制限があり、その理由を必ず説明してください。

* System.Int32これはC#の質問なので、.NET名の代わりにC#エイリアスが使用されます。

注:.NET開発者からブログまたは記事がありましたが(これは見つかりません)、限られた数の算術関数と、彼らがそれを心配しなかったいくつかの理由を指摘しました。私が覚えているように、彼らは他のデータ型のサポートを追加する計画がないことを示しました。

注:Javaは符号なしデータ型をサポートしておらず、以前は8ビットまたは16ビットの整数をサポートしていませんでした。多くのC#開発者はJavaのバックグラウンドを持つか、両方の言語で作業する必要があるため、一方の言語の制限が人為的に他方に課せられることがあります。


私の一般的な経験則は、単純に「できなければintを使用する」です。
PerryC

@PerryCそれが最も一般的な慣習だと思います。私の答えのポイントは、言語機能を使用できるようにするより完全な規則を提供することでした。
18:39に

6

主に2つのことに注意する必要があります:表すデータ、および計算の中間ステップ。

unsigned int私たちは通常、負の年齢を考慮しないため、年齢がであることは確かに理にかなっています。しかし、あなたは別の年齢から1つの年齢を引くことに言及します。ある整数から別の整数を盲目的に差し引くだけであれば、負の年齢が意味をなさないことに以前同意したとしても、負の数になることは間違いなくあり得ます。したがって、この場合、符号付き整数を使用して計算を実行する必要があります。

符号なしの値が悪いかどうかに関しては、符号なしの値が悪いと言うのは非常に一般化されていると思います。あなたが言ったように、Javaには符号なしの値はありません。Aの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

ご覧のとおり、有効な値で2バイトを追加し、有効な値で1バイトになると、符号が変更されます。それだけでなく、70 + 80 == -106であることはすぐにはわかりません。技術的にはこれはオーバーフローですが、(人間として)私の考えでは、0xFF未満の値のバイトはオーバーフローしないはずです。紙の上でビット演算を行うとき、8ビット目を符号ビットとは見なしません。

私はビットレベルで多くの整数を扱っていますが、すべてに署名すると、通常、すべてが直感的で扱いにくくなり1ます。一方、符号なし整数を右シフトしても、それは行われません。例えば:

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
符号なし整数型のない言語(特にバイト)についての不満を共有しますが、これはここで尋ねられた質問に対する直接的な答えではないのではないかと思います。たぶん、あなたは、私は信じているという結論を、追加することができ、次のようになります。「だビットとしての価値の考え方とは、符号付き整数あなた場合は、Use符号なし整数であれば、あなたの数字としてそれらについてだ思考。」
5gon12eder

1
上記のコメントで言ったことです。他の誰かが同じように考えているのを見てうれしい。
ロバートブリストージョンソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.