9

失敗:

object o = ((1==2) ? 1 : "test");

成功:

object o;
if (1 == 2)
{
    o = 1;
}
else
{
    o = "test";
}

最初のステートメントのエラーは次のとおりです。

'int' と 'string' の間に暗黙的な変換がないため、条件式の型を特定できません。

なぜ必要なのですか。これらの値をオブジェクト型の変数に割り当てています。

編集:上記の例は些細なことですが、これが非常に役立つ例があります:

int? subscriptionID; // comes in as a parameter

EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32)
{
    Value = ((subscriptionID == null) ? DBNull.Value : subscriptionID),
}
4

4 に答える 4

17

使用する:

object o = ((1==2) ? (object)1 : "test");

問題は、条件演算子の戻り値の型を明確に判断できないことです。つまり、int と string の間で最適な選択はありません。コンパイラは常に真の式の型を使用し、必要に応じて偽の式を暗黙的にキャストします。

編集: あなたの2番目の例では:

int? subscriptionID; // comes in as a parameter

EntityParameter p1 = new EntityParameter("SubscriptionID", DbType.Int32)
{
    Value = subscriptionID.HasValue ? (object)subscriptionID : DBNull.Value,
}

PS:
それは「三項演算子」とは呼ばれません。三項演算子です、「条件演算子」と呼ばれています。

于 2009-11-03T19:09:38.923 に答える
15

他の答えは正しいですが、それらが真実で関連性のあるステートメントを作成しているという意味では、ここにはまだ表現されていない言語設計の微妙な点がいくつかあります。現在の条件演算子の設計には、さまざまな要因が影響しています。

まず、できるだけ多くの式が、式の内容のみから判別できる明確な型を持つことが望ましいです。これは、いくつかの理由から望ましいことです。たとえば、IntelliSense エンジンの構築がはるかに簡単になります。入力すると、IntelliSense はsome-expressionx.M(some-expression.を分析し、その型を決定し、xM が参照するメソッドを IntelliSense が認識する前にドロップダウンを生成できる必要があります。IntelliSense は、すべての引数を確認するまで M がオーバーロードされている場合、xM が何を参照しているのかを確実に知ることができませんが、最初の引数さえまだ入力していません。

2 つ目は、型情報が「内側から外側」に流れることを好みます。これは、まさに先ほど述べたシナリオ、つまりオーバーロード解決のためです。次の点を考慮してください。

void M(object x) {}
void M(int x) {}
void M(string x) {}
...
M(b ? 1 : "hello");

これは何をすべきですか?オブジェクトのオーバーロードを呼び出す必要がありますか? 時には文字列のオーバーロードを呼び出し、時には int のオーバーロードを呼び出す必要がありますか? たとえば、別のオーバーロードがあったM(IComparable x)場合はどうしますか? いつそれを選択しますか?

型情報が「双方向に流れる」場合、事態は非常に複雑になります。「これを object 型の変数に割り当てているので、コンパイラーは object を型として選択しても問題ないことを認識しているはずです」と言っても意味がありません。多くの場合、割り当て先の変数の型がわからないことがあります。オーバーロードの解決は、まさに引数の型から、引数を割り当てる変数であるパラメーターの型を解決するプロセスです。引数の型が割り当てられている型に依存する場合、推論に循環性があります。

ラムダ式の場合、型情報は「双方向に流れます」。それを効率的に実装するのに、1 年の大半を費やしました。私は、式が使用される可能性のあるコンテキストに基づいて、型情報が複雑な式に流れ込む場所を分析できるコンパイラを設計および実装する際のいくつかの困難について説明する長い一連の記事を書きました。パート1はここにあります:

http://blogs.msdn.com/ericlippert/archive/2007/01/10/lambda-expressions-vs-anonymous-methods-part-one.aspx

あなたは、「そうですね、私がオブジェクトに代入しているという事実がコンパイラによって安全に使用できない理由を理解しています。また、式が明確な型を持つ必要がある理由も理解していますが、型がそうでないのはなぜですか? int と string の両方がオブジェクトに変換可能であるため、式オブジェクトの?" これは私の 3 番目のポイントにつながります。

第 3 に、微妙ではあるが一貫して適用されている C# の設計原則の 1 つは、「魔法によって型を生成しない」ことです。型を決定する必要がある式のリストが与えられた場合、決定する型は常にリストのどこかにあります。新しいタイプを作成して選択することは決してありません。あなたが得るタイプは常にあなたが私たちに選んだものです。型のセットから最適な型を見つけると言う場合、その型のセットで最適な型を見つけます。セット {int, string} には、たとえば「動物、カメ、哺乳類、ワラビー」のように、最も一般的な型はありません。この設計上の決定は、条件演算子、型推論の統合シナリオ、暗黙的に型指定された配列型の推論などに適用されます。

この設計上の決定の理由は、通常の人間が、最適な型を決定する必要がある特定の状況でコンパイラが何をしようとしているのかを理解しやすくするためです。すぐそこにいて、あなたの顔をじっと見つめているタイプが選択されることがわかっている場合、何が起こるかを理解するのははるかに簡単です.

また、競合が発生した場合に、一連の型の中で最も一般的な型は何かについて、多くの複雑なルールを作成する必要がなくなります。タイプ {Foo, Bar} があり、両方のクラスが IBlah を実装し、両方のクラスが Baz を継承しているとします。両方が実装されている IBlah と、両方が拡張されている Baz のどちらが最も一般的な型ですか? この質問に答える必要はありません。私たちはそれを完全に避けたいと思っています。

最後に、C# コンパイラは実際には型の決定を微妙に間違っていることに注意してください。それについての私の最初の記事はここにあります:

http://blogs.msdn.com/ericlippert/archive/2006/05/24/type-in​​ference-woes-part-one.aspx

実際には、コンパイラが正しく実行し、仕様が間違っていることは議論の余地があります。私の意見では、実装設計は仕様設計よりも優れています。

とにかく、これは、三項演算子のこの特定の側面を設計する理由のほんの一部です。ここには他にも微妙な点があります。たとえば、特定の分岐パスのセットがスタック上のすべての可能なパスで正しい型を残すことが保証されているかどうかを CLR ベリファイアが判断する方法などです。それを詳細に議論することは、私をかなり遠ざけるでしょう.

于 2009-11-04T07:54:22.090 に答える
2

なぜ機能 X がこのようになるのかという質問は、しばしば非常に答えにくいものです。実際の動作に答える方がはるかに簡単です。

理由についての私の経験に基づいた推測。条件演算子は、ブール式を簡潔かつ簡潔に使用して、2 つの関連する値を選択できます。それらは単一の場所で使用されているため、関連している必要があります。ユーザーが代わりに2つの無関係な値を選択した場合、おそらくコードに微妙なタイプミス/バグがあり、コンパイラーはオブジェクトに暗黙的にキャストするのではなく、これを警告する方が適切です。これは、彼らが予期していなかったことかもしれません。

于 2009-11-03T19:16:35.207 に答える
0

「int」はプリミティブ型であり、オブジェクトではありませんが、「string」は「プリミティブオブジェクト」と見なされます。「objecto=1」のようなことをすると、実際には「int」を「Int32」にボックス化することになります。ボクシングに関する記事へのリンクは次のとおりです。

http://msdn.microsoft.com/en-us/magazine/cc301569.aspx

一般的に、追跡が難しいパフォーマンスの低下のため、ボクシングは避ける必要があります。

三項式を使用する場合、コンパイラーは、最終的な型が何であるかを判別するために代入変数をまったく調べません。元のステートメントをコンパイラーが実行しているものに分解するには、次のようにします。

ステートメント:オブジェクトo =((1 == 2)?1: "テスト");

コンパイラ:

  1. '((1 == 2)?1: "test")'の「1」と「test」の種類は何ですか?それらは一致しますか?
  2. #1の最後の型は、「オブジェクトo」の代入演算子型と一致しますか?

コンパイラは#1が完了するまで#2を評価しないため、失敗します。

于 2009-11-03T20:25:45.073 に答える