C#とJavaの三項演算子の違い(?:)


94

私はC#初心者ですが、問題が発生しました。三項演算子(? :)を扱う場合、C#とJavaには違いがあります。

次のコードセグメントで、4行目が機能しないのはなぜですか?コンパイラはのエラーメッセージを表示しますthere is no implicit conversion between 'int' and 'string'。5行目も機能しません。どちらListもオブジェクトですよね。

int two = 2;
double six = 6.0;
Write(two > six ? two : six); //param: double
Write(two > six ? two : "6"); //param: not object
Write(two > six ? new List<int>() : new List<string>()); //param: not object

ただし、同じコードがJavaで機能します。

int two = 2;
double six = 6.0;
System.out.println(two > six ? two : six); //param: double
System.out.println(two > six ? two : "6"); //param: Object
System.out.println(two > six ? new ArrayList<Integer>()
                   : new ArrayList<String>()); //param: Object

C#のどの言語機能が不足していますか?もしあれば、なぜ追加されないのですか?


4
どおりmsdn.microsoft.com/en-us/library/ty67wk28.aspx「のいずれかfirst_expressionとsecond_expressionの種類は同じである必要があり、または暗黙的な変換は、他の1つのタイプから存在している必要があります。」
エゴール2016

2
私はしばらくC#を実行していませんが、引用したメッセージはそれを言っていませんか?三項の2つのブランチは同じデータ型を返す必要があります。あなたはそれにintとStringを与えています。C#は、intを文字列に自動的に変換する方法を知りません。明示的な型変換を行う必要があります。
ジェイ

2
@ blackr1234 intはオブジェクトではないためです。それはプリミティブです。
Code-Apprentice

3
@ Code-Apprenticeはい、実際には非常に異なります-C#にはランタイムの具体化があるため、リストは無関係なタイプです。また、必要に応じて、ジェネリックインターフェイスの共分散/反変をミックスに投入して、ある程度の関係を導入することもできます;)
Lucas Trzesniewski

3
Javaの手段で型消去:OPの利益のためにArrayList<String>ArrayList<Integer>ばかりになるArrayListバイトコードに。これは、実行時にそれらがまったく同じ型であるように見えることを意味します。どうやらC#ではそれらは異なるタイプです。
Code-Apprentice

回答:


106

覗きC#5言語仕様のセクション7.14:条件演算子、我々は以下を参照してくださいすることができます:

  • xのタイプがXで、yのタイプがYの場合

    • 暗黙的な変換(§6.1)がXからYに存在するが、YからXには存在しない場合、Yは条件式のタイプです。

    • 暗黙的な変換(§6.1)がYからXには存在するが、XからYには存在しない場合、Xは条件式のタイプです。

    • そうでない場合、式のタイプを判別できず、コンパイル時エラーが発生します

つまり、xとyを相互に変換できるかどうかを調べようとします。変換できない場合は、コンパイルエラーが発生します。今回のケースintstringは、明示的または暗黙的な変換がないため、コンパイルされません。

これをJava 7言語仕様のセクション15.25と比較してください。条件演算子

  • 2番目と3番目のオペランドが同じ型(null型の場合もある)である場合、それは条件式の型です。(いいえ
  • 2番目と3番目のオペランドの一方がプリミティブ型Tであり、もう一方の型がTにボクシング変換(§5.1.7)を適用した結果である場合、条件式の型はTです(NO)。
  • 2番目と3番目のオペランドの一方がNULL型で、もう一方の型が参照型である場合、条件式の型はその参照型です。(いいえ
  • そうではなく、2番目と3番目のオペランドが数値型に変換可能な型(§5.1.8)を持っている場合、いくつかのケースがあります:(NO
  • それ以外の場合、2番目と3番目のオペランドは、それぞれタイプS1とS2です。T1をS1にボクシング変換を適用した結果のタイプとし、T2をS2にボクシング変換を適用した結果のタイプとします。
    条件式のタイプは、lub(T1、T2)(§15.12.2.7)にキャプチャ変換(§5.1.10)を適用した結果です。(はい

そして、セクション15.12.2.7を見てください。実際の引数に基づいて型引数を推論することで、それを使用して着陸する呼び出しに使用される型として機能する共通の祖先を見つけようとしていることがわかりますObjectObject 許容できる引数なので、呼び出しは機能します。


1
私はC#仕様を読んで、少なくとも1つの方向に暗黙的な変換が必要であると述べました。コンパイラエラーは、どちらの方向にも暗黙の変換がない場合にのみ発生します。
Code-Apprentice

1
もちろん、仕様から直接引用するので、これはまだ最も完全な答えです。
Code-Apprentice

これは実際にはJava 5でのみ変更されました(おそらく6でした)。それ以前は、Javaの動作はC#とまったく同じでした。列挙型の実装方法が原因で、これはおそらくC#よりもJavaでさらに多くの驚きを引き起こしましたが、それでもずっと良い変更です。
Voo 2016

@Voo:FYI、Java 5、Java 6のスペックバージョンは同じ(Java言語仕様、第3版)であるため、Java 6でも変更されていません
ruakh

86

与えられた答えは良いです。このC#の規則は、より一般的な設計ガイドラインの結果であると彼らに付け加えます。いくつかの選択肢の1つから式のタイプを推測するように求められると、C#はその中から最良のものを選択します。つまり、C#に「キリン、哺乳類、動物」などの選択肢を与えると、状況に応じて、最も一般的な-動物-または最も具体的な-キリン-が選択される可能性があります。しかし、実際に与えられた選択肢の1つを選択する必要があります。C#は、「私の選択は猫と犬の間です。したがって、私は動物が最良の選択であると推測します」とは決して言いません。それは与えられた選択ではなかったので、C#はそれを選択できません。

三項演算子の場合、C#はintとstringのより一般的なタイプを選択しようとしますが、どちらも一般的なタイプではありません。C#は、最初は選択肢ではなかった型(オブジェクトなど)を選択するのではなく、型を推測できないと判断します。

また、これはC#の別の設計原則に沿ったものであることに注意してください。言語は「私があなたが何を意味しているかを推測して、できれば何とかするつもりだ」とは言っていません。言語は「あなたがここで紛らわしい何かを書いたと思います、そして私はそれについてあなたに話すつもりです」と言います。

また、C#は変数から割り当てられた値への推論ではなく、逆の方向に進むことに注意します。C#は「オブジェクト変数に割り当てているので、式はオブジェクトに変換可能でなければならないので、それが確実に変換される」とは述べていません。むしろ、C#は「この式には型が必要であり、その型がオブジェクトと互換性があると推測できる必要がある」と述べています。式に型がないため、エラーが発生します。


6
最初のパラグラフを通り過ぎて、なぜ突然の共分散/反変の関心があるのか​​疑問に思ってから、2番目のパラグラフについてさらに同意し始めてから、誰が答えを書いたのかを確認しました...すぐに推測したはずです。例として「キリン」をどれだけ使用するか正確に気づいたことがありますか?あなたは本当にキリンが好きでなければなりません。
Pharap、2016

21
キリンは素晴らしいです!
Eric Lippert、2016

9
@Pharap:すべての真面目に、私がキリンをそんなに使うのには教育的な理由があります。まず、キリンは世界中で有名です。第二に、それは一種の愉快な言葉であり、それらは非常に奇妙に見えるので、それは思い出に残るイメージです。第3に、たとえば「キリン」と「カメ」を同じ類推で使用すると、「これらの2つのクラスは基本クラスを共有する場合がありますが、非常に異なる特性を持つ非常に異なる動物です」と強く強調されます。型システムに関する質問には、型が論理的に非常に似ている例が含まれていることが多く、これにより直観が間違った方向に進みます。
Eric Lippert、2016

7
@Pharap:つまり、質問は通常「顧客の請求書プロバイダーファクトリーのリストを請求書プロバイダーファクトリーのリストとして使用できないのはなぜですか?」のようなものです。そして、あなたの直感は「ええ、それらは基本的に同じものです」と言いますが、「なぜキリンでいっぱいの部屋を哺乳類でいっぱいの部屋として使用できないのですか?」と言うとき。すると、「哺乳動物でいっぱいの部屋にはトラがいるので、キリンでいっぱいの部屋はできないので」と言うと、より直感的な類推になります。
Eric Lippert、2016

6
@Eric元々この問題に遭遇しましたint? b = (a != 0 ? a : (int?) null)。リンクされているすべての質問とともに、この質問もあります。リンクをたどると、かなりの数があります。それに比べて、Javaのやり方で実際の問題に遭遇する人のことは聞いたことがありません。
BlueRaja-Danny Pflughoeft 2016

24

ジェネリック部分について:

two > six ? new List<int>() : new List<string>()

C#では、コンパイラーは右側の式部分をいくつかの一般的な型に変換しようとします。以来、List<int>及びList<string>二つの異なる構築型で、一方が他方に変換することができません。

Javaでは、コンパイラーは変換ではなく共通のスーパータイプ見つけようとします。そのため、コードのコンパイルには、ワイルドカード型消去の暗黙的な使用が含まれます。

two > six ? new ArrayList<Integer>() : new ArrayList<String>()

のコンパイルタイプArrayList<?>(実際には、両方とも一般的なジェネリックスーパータイプであるため、使用コンテキストに応じて、ArrayList<? extends Serializable>またはArrayList<? extends Comparable<?>>)と実行時のrawタイプArrayList(一般的なrawスーパータイプであるため)があります。

たとえば(自分でテスト)

void test( List<?> list ) {
    System.out.println("foo");
}

void test( ArrayList<Integer> list ) { // note: can't use List<Integer> here
                                 // since both test() methods would clash after the erasure
    System.out.println("bar");
}

void test() {
    test( true ? new ArrayList<Object>() : new ArrayList<Object>() ); // foo
    test( true ? new ArrayList<Integer>() : new ArrayList<Object>() ); // foo 
    test( true ? new ArrayList<Integer>() : new ArrayList<Integer>() ); // bar
} // compiler automagically binds the correct generic QED

実際にWrite(two > six ? new List<object>() : new List<string>());は機能しません。
blackr1234 2016

2
@ blackr1234正確:他の答えがすでに言ったことを繰り返したくありませんでしたが、3項式は型に評価される必要があり、コンパイラーは2つの「最小公分母」を見つけようとしません。
Mathieu Guindon、2016

2
実際、これは型の消去についてではなく、ワイルドカード型についてです。消去ArrayList<String>ArrayList<Integer>なるであろうArrayList(生型)が、この三項演算子のための推論型があるArrayList<?>(ワイルドカードタイプ)。より一般的には、Java ランタイムが型消去によってジェネリックスを実装することは、式のコンパイル時の型には影響しません。
メリトン

1
@vaxquisありがとう!私はC#の人です。Javaジェネリックは混乱します;-)
Mathieu Guindon

2
実際には、のタイプtwo > six ? new ArrayList<Integer>() : new ArrayList<String>()IS ArrayList<? extends Serializable&Comparable<?>>あなたは型の変数に割り当てることを意味ArrayList<? extends Serializable>だけでなく、型の変数ArrayList<? extends Comparable<?>>が、もちろん、ArrayList<?>と等価である、ArrayList<? extends Object>としても機能します。
Holger

6

JavaとC#の両方(および他のほとんどの言語)では、式の結果には型があります。三項演算子の場合、結果に対して評価される2つの可能な副次式があり、両方が同じ型でなければなりません。Javaの場合、オートボクシングによってint変数をに変換できますIntegerIntegerとの両方がString継承Objectされるため、単純なナローイング変換で同じ型に変換できます。

一方、C#では、an intはプリミティブであり、stringまたはへの暗黙的な変換はありませんobject


答えてくれてありがとう。オートボクシングは私が現在考えていることです。C#はオートボクシングを許可していませんか?
blackr1234 2016

2
intをオブジェクトにボックス化することはC#では完全に可能であるため、これは正しくないと思います。ここでの違いは、C#が許可されているかどうかを判断する際に、非常にのんびりしたアプローチを取るだけだと私は考えています。
Jeroen Vannevel 2016

9
これは自動ボクシングとは何の関係もありません。これはC#でも同様です。違いは、C#はJava(Java 5のみなので!)が検索するのに対して、共通のスーパータイプを検索しようとしないことです。これは、2つのカスタムクラス(どちらもオブジェクトから継承)で試した場合に何が起こるかを確認することで簡単にテストできます。また、からintへの暗黙的な変換もありobjectます。
Voo 2016

3
そして、もう少しちょっと気をつけましょう:Integer/Stringto からの変換はナObjectローイング変換ではありません、それは正反対です:-)
Voo

1
IntegerそしてStringまた、実装SerializableおよびComparableそれらのいずれかへの割り当ては、同様に例えば動作しますので、Comparable<?> c=condition? 6: "6";またはList<? extends Serializable> l = condition? new ArrayList<Integer>(): new ArrayList<String>();法的なJavaコードです。
Holger

5

これは非常に簡単です。stringとintの間の暗黙的な変換はありません。三項演算子では、最後の2つのオペランドが同じ型である必要があります。

試してください:

Write(two > six ? two.ToString() : "6");

11
問題は、JavaとC#の違いの原因です。これは質問の答えにはなりません。
Jeroen Vannevel 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.