34

以前、今日私は2つのushortを追加しようとしていましたが、結果をushortにキャストし直す必要があることに気付きました。(意図しないオーバーフローを防ぐために)uintになったのではないかと思いましたが、驚いたことに、それはint(System.Int32)でした。

これにはいくつかの賢い理由がありますか、それともintが「基本的な」整数型と見なされているためでしょうか?

例:

ushort a = 1;
ushort b = 2;

ushort c = a + b; // <- "Cannot implicitly convert type 'int' to 'ushort'. An explicit conversion exists (are you missing a cast?)"
uint d = a + b; // <- "Cannot implicitly convert type 'int' to 'uint'. An explicit conversion exists (are you missing a cast?)"

int e = a + b; // <- Works!

編集:GregSの回答が言うように、C#仕様では、両方のオペランド(この例では「a」と「b」)をintに変換する必要があるとされています。これが仕様の一部である理由の根本的な理由に興味があります。C#仕様でushort値を直接操作できないのはなぜですか。

4

5 に答える 5

67

シンプルで正しい答えは、「C# 言語仕様にそう書いてあるから」です。

明らかに、あなたはその答えに満足しておらず、「なぜそう言っているのか」を知りたがっています。「信頼できるおよび/または公式の情報源」を探していますが、それは少し難しいでしょう. これらの設計上の決定はずっと前になされたもので、13 年というのはソフトウェア エンジニアリングの長い人生です。Eric Lippert が呼んでいるように、それらは「古いタイマー」によって作成されました。彼らはより大きくより良いものに移行しており、公式の情報源を提供するためにここに回答を投稿していません.

ただし、単に信頼できるというリスクがありますが、推測することはできます。C# などのマネージ コンパイラには、.NET 仮想マシン用のコードを生成する必要があるという制約があります。そのルールは、CLI 仕様で慎重に (そして非常に読みやすく) 説明されています。これは Ecma-335 仕様で、ここから無料でダウンロードできます。

パーティション III の 3.1 章と 3.2 章に目を向けてください。addこれらは、加算を実行するために使用できる 2 つの IL 命令について説明していますadd.ovf。表 2「2 進数演算」へのリンクをクリックすると、これらの IL 命令で使用できるオペランドが説明されています。そこにリストされているタイプはほんのわずかであることに注意してください。byte と short だけでなく、すべての unsigned 型が欠落しています。int、long、IntPtr、および浮動小数点 (float と double) のみが許可されます。x でマークされた追加の制約を使用すると、int を long に追加することはできません。これらの制約は完全に人為的なものではなく、利用可能なハードウェアで合理的に効率的に実行できることに基づいています。

マネージ コンパイラは、有効な IL を生成するためにこれに対処する必要があります。それは難しいことではありません。ushort をテーブルにあるより大きな値の型に変換するだけです。この変換は常に有効です。C# コンパイラは、表に示されている次に大きな型である int を選択します。または一般に、いずれかのオペランドを次に大きい値の型に変換して、両方が同じ型になり、表の制約を満たすようにします。

しかし、ここで新たな問題が発生しました。C# プログラマーをかなり悩ませる問題です。加算の結果は昇格型です。あなたの場合、それはintになります。したがって、たとえば 0x9000 と 0x9000 の 2 つの ushort 値を追加すると、完全に有効な int の結果 0x12000 が得られます。問題は、それが ushort に収まらない値であることです。値がオーバーフローしました。しかし、IL の計算ではオーバーフローしませんでした。オーバーフローするのは、コンパイラが ushort に詰め込もうとしたときだけです。0x12000 は 0x2000 に切り捨てられます。10ではなく、2本または16本の指で数えたときにのみ意味をなす、当惑するような異なる値.

add.ovf 命令がこの問題に対処していないことは注目に値します。オーバーフロー例外を自動発生させる命令です。しかし、そうではありません。変換された int の実際の計算はオーバーフローしませんでした。

ここで、実際の設計上の決定が行われます。昔の人たちは、単に int の結果を ushort に切り捨てることはバグ工場だと判断したようです。確かにそうです。彼らは、加算がオーバーフローする可能性があり、それが発生しても大丈夫であることを知っていることを認めなければならないと判断しました. 彼らはそれをあなたの問題にしました。主な理由は、それを自分たちのものにし、それでも効率的なコードを生成する方法を知らなかったからです。キャストする必要があります。はい、それは腹立たしいことです。あなたもその問題を望んでいないと確信しています。

注目に値するのは、VB.NET の設計者がこの問題に対して別の解決策をとったことです。彼らは実際にそれ問題にし、責任を負いませんでした。2 つの UShort を追加して、キャストなしで UShort に割り当てることができます。違いは、VB.NET コンパイラが実際に追加の IL を生成してオーバーフロー状態をチェックすることです。これは安価なコードではなく、すべての短い追加が約 3 倍遅くなります。しかし、それ以外の点では、Microsoft が非常に類似した機能を持つ2 つの言語を維持している理由を説明しています。

簡単に言うと、最新の cpu アーキテクチャとあまりよく一致しないタイプを使用しているため、代償を払っています。それ自体が、ushort の代わりに uint を使用する本当に良い理由です。ushort から牽引力を引き出すのは困難です。それらを操作するコストがメモリの節約を上回る前に、それらの多くが必要になります。CLI 仕様が制限されているだけでなく、x86 コアは、マシン コードのオペランド プレフィックス バイトが原因で、16 ビット値をロードするために余分な CPU サイクルを必要とします。それが今日でも当てはまるかどうかは実際にはわかりませんが、サイクルのカウントにまだ注意を払っていたときに戻ってきました. 一年前の犬。


VB.NET コンパイラが生成するのと同じコードを C# コンパイラに生成させることで、これらの見苦しくて危険なキャストについては安心できることに注意してください。したがって、キャストが賢明でないことが判明した場合、OverflowException が発生します。[プロジェクト] > [プロパティ] > [ビルド] タブ > [詳細設定] ボタン > [算術オーバーフロー/アンダーフローをチェック] チェックボックスをオンにします。デバッグ ビルド専用です。このチェックボックスがプロジェクト テンプレートによって自動的にオンにならない理由は、もう 1 つの非常に不可解な質問です。決定はかなり前に行われました。

于 2012-04-14T21:28:52.540 に答える
19
ushort x = 5, y = 12;

次の代入ステートメントは、代入演算子の右辺の算術式がデフォルトでintと評価されるため、コンパイルエラーを生成します。

ushort z = x + y;   // Error: conversion from int to ushort

http://msdn.microsoft.com/en-us/library/cbf1574z(v=vs.71).aspx

編集:
ushortの算術演算の場合、オペランドはすべての値を保持できるタイプに変換されます。オーバーフローを回避できるようにします。オペランドは、int、uint、long、ulongの順に変更できます。C#言語仕様を参照してください。このドキュメントでは、セクション4.1.5整数型(ワードドキュメントの80ページ前後)に進んでください。ここにあなたが見つけるでしょう:

バイナリ+、–、*、/、%、&、^、|、==、!=、>、<、> =、および<=演算子の場合、オペランドはタイプTに変換されます。ここでTは最初の演算子です。両方のオペランドのすべての可能な値を完全に表すことができるint、uint、long、およびulongの。次に、タイプTの精度を使用して操作が実行され、結果のタイプはT(または関係演算子の場合はbool)になります。二項演算子を使用して、一方のオペランドをlong型にし、もう一方をulong型にすることはできません。

エリックリッパーは質問で述べています

C#では、算術演算がショートパンツで行われることはありません。算術演算はint、uints、longs、およびulongsで実行できますが、算術演算はshortsでは実行されません。前に述べたように、算術計算の大部分はintに収まるため、shortはintに昇格し、算術はintで行われます。大多数はショートに収まりません。短い演算は、int用に最適化された最新のハードウェアではおそらく遅くなり、短い演算はそれ以下のスペースを占有しません。チップ上でintまたはlongで実行されます。

于 2012-04-08T18:36:56.537 に答える
6

C# 言語仕様から:

7.3.6.2 2 進数の昇格 2 進数の昇格は、定義済みの +、–、*、/、%、&、|、^、==、!=、>、<、>=、および <= の 2 項演算子のオペランドに対して発生します。 . バイナリ数値昇格では、暗黙的に両方のオペランドが共通の型に変換されます。非関係演算子の場合、この型は演算の結果の型にもなります。2 進数の昇格は、次の規則をここに表示されている順序で適用することで構成されます。

· 一方のオペランドが decimal 型の場合、もう一方のオペランドは decimal 型に変換されます。または、もう一方のオペランドが float または double 型の場合は、バインド時エラーが発生します。

· それ以外の場合、いずれかのオペランドが double 型の場合、もう一方のオペランドは double 型に変換されます。

· それ以外の場合、いずれかのオペランドが float 型の場合、もう一方のオペランドは float 型に変換されます。

· それ以外の場合、いずれかのオペランドが ulong 型の場合、もう一方のオペランドは ulong 型に変換されます。または、もう一方のオペランドが sbyte、short、int、または long 型の場合はバインド時エラーが発生します。

· それ以外の場合、いずれかのオペランドが long 型の場合、もう一方のオペランドは long 型に変換されます。

· 一方のオペランドが uint 型で、もう一方のオペランドが sbyte、short、または int 型の場合、両方のオペランドが long 型に変換されます。

· それ以外の場合、いずれかのオペランドが uint 型の場合、もう一方のオペランドは uint 型に変換されます。

· それ以外の場合、両方のオペランドが int 型に変換されます。

于 2012-04-08T18:42:11.467 に答える
3

意図されている理由はありません。これは単なる効果であるか、引数に適合する暗黙の変換が存在するパラメーターの最初のオーバーロードが使用されることを示すオーバーロード解決の規則を適用することです。

これは、C# 仕様のセクション 7.3.6 に次のように記載されています。

数値昇格は別個のメカニズムではなく、事前定義された演算子にオーバーロード解決を適用する効果です。

例を挙げて説明を続けます。

数値昇格の例として、2 項 * 演算子の定義済みの実装を考えてみましょう。

int 演算子 *(int x, int y);

uint 演算子 *(uint x, uint y);

長い演算子 *(長い x, 長い y);

ulong 演算子 *(ulong x, ulong y);

float operator *(float x, float y);

double operator *(double x, double y);

10 進数演算子 *(10 進数 x、10 進数 y);

オーバーロードの解決規則 (§7.5.3) がこの一連の演算子に適用されると、オペランドの型から暗黙的な変換が存在する最初の演算子が選択されます。たとえば、操作 b * s (b はバイト、s は short) の場合、オーバーロードの解決では、演算子 *(int, int) が最適な演算子として選択されます。

于 2012-04-08T19:03:23.343 に答える
2

実際、あなたの質問は少し注意が必要です。この仕様が言語の一部である理由は...彼らが言語を作成したときにその決定を下したからです。これは残念な答えのように聞こえますが、それはまさにその通りです。


ただし、実際の答えには、1999年から2000年の当時の多くのコンテキスト決定が含まれる可能性があります。C#を作成したチームは、これらすべての言語の詳細についてかなり活発な議論を行ったと確信しています。

  • ..。
  • C#は、シンプルでモダンな汎用のオブジェクト指向プログラミング言語を目的としています。
  • ソースコードの移植性は、プログラマーの移植性と同様に非常に重要です。特に、CおよびC++に既に精通しているプログラマーにとっては重要です。
  • 国際化のサポートは非​​常に重要です。
  • ..。

上記の引用はウィキペディアC#からのものです

これらの設計目標はすべて、彼らの決定に影響を与えた可能性があります。たとえば、2000年には、システムのほとんどがすでにネイティブ32ビットであったため、算術演算を実行するときに32ビットで変換されるため、変数の数をそれよりも少なく制限することを決定した可能性があります。これは一般的に遅いです。

その時点で、あなたは私に尋ねるかもしれません。それらのタイプに暗黙の変換がある場合、なぜそれらを含めたのですか?上で引用したように、彼らの設計目標の1つは、移植性です。

したがって、古いCまたはC ++プログラムの周りにC#ラッパーを作成する必要がある場合は、いくつかの値を格納するためにそれらの型が必要になる場合があります。その場合、これらのタイプは非常に便利です。

これは、Javaが下さなかった決定です。たとえば、C ++プログラムと相互作用してushort値を受け取るJavaプログラムを作成する場合、Javaにはshort(署名済み)しかないため、簡単に相互に割り当てて正しい値を期待することはできません。

確かに、Javaでそのような値を受け取ることができる次の利用可能な型はint(もちろん32ビット)です。ここでメモリが2倍になりました。これは大したことではないかもしれませんが、代わりに100000要素の配列をインスタンス化する必要があります。

実際、これらの決定は、あるものから別のものへのスムーズな移行を提供するために、過去と未来を見ることによって行われることを覚えておく必要があります。

しかし今、私は最初の質問から分岐していると感じています。


ですから、あなたの質問は良いものであり、あなたが聞きたかったことではないかもしれないと私が知っていても、うまくいけば私はあなたにいくつかの答えをもたらすことができました。

必要に応じて、以下のリンクからC#仕様の詳細を読むこともできます。あなたにとって興味深いかもしれないいくつかの興味深いドキュメントがあります。

整数型

チェックされた演算子とチェックされていない演算子

暗黙の数値変換テーブル

ちなみに、最初の質問には適切なリンクでかなり良い答えを提供してくれたので、おそらくhabib-osuに報酬を与えるべきだと思います。:)

よろしく

于 2012-04-14T03:52:11.483 に答える