null + trueは文字列ですか?


112

以来はtrueどのように、文字列型ではありませんnull + true文字列?

string s = true;  //Cannot implicitly convert type 'bool' to 'string'   
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'

この背後にある理由は何ですか?


27
意味のないことをして、コンパイラが生成するメッセージが気に入らないのですか?これは無効なC#コードです...正確に何をしたいですか?
ホーガン

19
@Hogan:質問は純粋な好奇心から問われるほど、コードはランダムで学術的であるように見えます。ちょうど私の推測...
BoltClock

8
null + trueはJavaScriptで1を返します。
Fatih Acet、

回答:


147

奇妙に思われるかもしれませんが、これは単にC#言語仕様のルールに従っているだけです。

セクション7.3.4から:

x op yという形式の演算(ここで、opはオーバーロード可能な2項演算子、xはX型の式、yはY型の式)は次のように処理されます。

  • 操作演算子op(x、y)のXおよびYによって提供される候補のユーザー定義演算子のセットが決定されます。このセットは、Xによって提供される候補演算子とYによって提供される候補演算子の和集合で構成され、それぞれが§7.3.5のルールを使用して決定されます。XとYが同じ型である場合、またはXとYが共通の基本型から派生している場合、共有候補の演算子は結合されたセットで1回だけ出現します。
  • 候補のユーザー定義演算子のセットが空でない場合、これは操作の候補演算子のセットになります。それ以外の場合は、事前定義された2項演算子opの実装(リフトされた形式を含む)が、操作の候補演算子のセットになります。特定の演算子の事前定義された実装は、演算子の説明で指定されています(§7.8から§7.12)。
  • §7.5.3のオーバーロード解決ルールが候補演算子のセットに適用され、引数リスト(x、y)に関して最適な演算子が選択され、この演算子がオーバーロード解決プロセスの結果になります。オーバーロードの解決で単一の最適な演算子を選択できない場合、バインディング時エラーが発生します。

それでは、順番に見ていきましょう。

Xはここではnull型です。つまり、そのように考えたい場合は、型ではありません。候補はありません。Y isはbool、ユーザー定義の+演算子を提供しません。したがって、最初のステップではユーザー定義の演算子は見つかりません。

次に、コンパイラーは2番目の箇条書きに進み、事前定義された2項演算子+実装とそのリフトされた形式を調べます。これらは、仕様のセクション7.8.4にリストされています。

これらの事前定義された演算子を確認すると、適用できるのはのみですstring operator +(string x, object y)。したがって、候補セットには単一のエントリがあります。これにより、最後の箇条書きが非常に簡単になります。オーバーロードの解決によりその演算子が選択され、全体的な式の型が与えられstringます。

興味深い点の1つは、言及されていない型で使用可能な他のユーザー定義演算子があったとしても、これが発生することです。例えば:

// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;

それは問題ありませんが、コンパイラがを調べることを知らないため、nullリテラルには使用されませんFoo。これstringは、仕様に明示的にリストされている事前定義された演算子であるため、考慮する必要があることのみを認識しています。(実際、これは文字列型で定義された演算子ではありません ... 1)つまり、これはコンパイルに失敗します:

// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;

他の第2オペランドタイプは、もちろん他のいくつかの演算子を使用します。

var x = null + 0; // x is Nullable<int>
var y = null + 0L; // y is Nullable<long>
var z = null + DayOfWeek.Sunday; // z is Nullable<DayOfWeek>

1 なぜ文字列+演算子がないのか疑問に思われるかもしれません。それは合理的な質問であり、私は答えを推測しているだけですが、次の表現を考えてください:

string x = a + b + c + d;

stringC#コンパイラーに特別なケースがない場合、これは効果的に終了します。

string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;

したがって、2つの不要な中間文字列が作成されます。ただし、コンパイラ内には特別なサポートがあるため、実際には上記を次のようにコンパイルできます。

string x = string.Concat(a, b, c, d);

正確な長さの単一の文字列を作成し、すべてのデータを1回だけコピーできます。いいね。


「文字列の全体的な表現型を与える」私は単に同意しません。エラーは、trueに変換できないことが原因stringです。式が有効な場合、タイプはになりますstringが、この場合、文字列への変換に失敗すると、式全体がエラーになり、タイプがありません。
leppie 2010

6
@leppie:式有効であり、その型は文字列です。「var x = null + true;」を試してください。-コンパイルされ、xタイプが決まりますstring。ここで使用される署名はstring operator+(string, object)-にではboolなくobject(これで問題ありません)に変換されstringます。
Jon Skeet

ジョン、ありがとう。ところで、仕様のバージョン2のセクション14.7.4。
leppie

@leppie:それはECMAナンバリングにありますか?それは常に非常に異なっていました:(
Jon Skeet

47
20分で。彼はこのすべてを20分で書いた。彼は本か何かを書くべきです...ああ待ってください。
Epaga

44

その理由は、いったん導入する+と、C#演算子バインディングルールが機能するようになるためです。+利用可能な演算子のセットを考慮し、最適なオーバーロードを選択します。これらの演算子の1つは次のとおりです

string operator +(string x, object y)

このオーバーロードは、式の引数の型と互換性がありますnull + true。したがって、それは演算子として選択され、本質的((string)null) + trueに評価されて値に評価されます"True"

C#言語仕様のセクション7.7.4には、この解決策に関する詳細が含まれています。


1
生成されたILがConcatを直接呼び出すことに気づきました。そこに+演算子関数の呼び出しがあると思っていました。それは単に最適化をインライン化するだけでしょうか?
クエンティンスターリン

1
私は理由がないことであると考えてい@qstarin 本当にoperator+ためにstring。代わりに、それはコンパイラーの頭の中にのみ存在し、それを単に呼び出しに変換しますstring.Concat
JaredPar

1
@qstarin @JaredPar:私の回答では、もう少し詳しく説明しました。
Jon Skeet、2010

11

コンパイラは、最初にnull引数をとることができるoperator +()を探しに出かけます。標準値タイプはどれも適格ではなく、nullはそれらの有効な値ではありません。唯一の一致はSystem.String.operator +()であり、あいまいさはありません。

その演算子の2番目の引数も文字列です。それはkapooeyに行き、暗黙的にブールを文字列に変換することはできません。


10

興味深いことに、生成されたものを検査するためにReflectorを使用すると、次のコード:

string b = null + true;
Console.WriteLine(b);

コンパイラによってこれに変換されます:

Console.WriteLine(true);

この「最適化」の背後にある推論は、私が言わなければならない少し奇妙であり、私が期待する演算子の選択に韻を踏むことはありません。

また、次のコード:

var b = null + true; 
var sb = new StringBuilder(b);

に変換されます

string b = true; 
StringBuilder sb = new StringBuilder(b);

string b = true;コンパイラーが実際に受け入れない場所。


8

nullnull文字列にキャストされ、ブールから文字列への暗黙のコンバーターtrueがあるため、文字列にキャストされ、次に+演算子が適用されます。これは次のようになります。

Ildasmで確認した場合:

string str = null + true;

それはうなり声です:

.locals init ([0] string str)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  box        [mscorlib]System.Boolean
  IL_0007:  call       string [mscorlib]System.String::Concat(object)
  IL_000c:  stloc.0

5
var b = (null + DateTime.Now); // String
var b = (null + 1);            // System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etc
var b = (null + new Object()); // String | same with any ref type

クレイジー?いいえ、その背後に理由があるに違いありません。

誰かが電話Eric Lippert...


5

これの理由は便利です(文字列の連結は一般的なタスクです)。

BoltClockが言ったように、「+」演算子は数値型、文字列で定義され、独自の型でも定義できます(演算子のオーバーロード)。

引数の型にオーバーロードされた '+'演算子がなく、それらが数値型でない場合、コンパイラーはデフォルトで文字列連結を行います。

String.Concat(...)'+'を使用して連結すると、コンパイラはへの呼び出しを挿入し、Concatの実装はそれに渡された各オブジェクトでToStringを呼び出します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.