true
は文字列型ではないので、null + true
はどのように文字列ですか?
string s = true; //Cannot implicitly convert type 'bool' to 'string'
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'
この背後にある理由は何ですか?
奇妙に思えるかもしれませんが、C#言語仕様のルールに従っているだけです。
セクション7.3.4から:
X op yの形式の演算。ここで、opはオーバーロード可能な二項演算子、xはタイプXの式、yはタイプYの式であり、次のように処理されます。
- 演算演算子op(x、y)に対してXおよびYによって提供される候補ユーザー定義演算子のセットが決定されます。セットは、Xによって提供される候補演算子とYによって提供される候補演算子の和集合で構成され、それぞれが§7.3.5の規則を使用して決定されます。 XとYが同じ型である場合、またはXとYが共通の基本型から派生している場合、共有候補演算子は結合されたセットで1回だけ発生します。
- 候補のユーザー定義演算子のセットが空でない場合、これが操作の候補演算子のセットになります。それ以外の場合は、リフトされた形式を含む、事前定義された二項演算子op実装が、操作の候補演算子のセットになります。特定の演算子の事前定義された実装は、演算子の説明で指定されています(§7.8から§7.12)。
- §7.5.3の過負荷解決ルールが候補演算子のセットに適用され、引数リスト(x、y)に関して最適な演算子が選択されます。この演算子は、過負荷解決プロセスの結果になります。過負荷解決で最適な演算子を1つ選択できない場合、バインド時エラーが発生します。
それでは、これを順番に見ていきましょう。
Xは、ここではnull型です。そのように考えたい場合は、型ではありません。候補者を提供していません。 Yはbool
であり、ユーザー定義の_+
_演算子を提供しません。したがって、最初のステップではユーザー定義の演算子は見つかりません。
次に、コンパイラーは2番目の箇条書きに進み、事前定義された2項演算子+実装とそれらのリフトされた形式を調べます。これらは仕様のセクション7.8.4にリストされています。
これらの事前定義された演算子を調べると、のみ該当するのはstring operator +(string x, object y)
です。したがって、候補セットには1つのエントリがあります。これにより、最終的な箇条書きが非常に単純になります...オーバーロード解決によってその演算子が選択され、全体的な式の型がstring
になります。
興味深い点の1つは、言及されていないタイプで使用可能な他のユーザー定義演算子がある場合でも、これが発生することです。例えば:
_// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;
_
これは問題ありませんが、コンパイラがFoo
を調べることを知らないため、nullリテラルには使用されません。 string
は仕様に明示的にリストされている事前定義された演算子であるため、それを考慮することだけがわかっています。 (実際、それはnot文字列型によって定義された演算子です。 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;
_
string
にC#コンパイラで特別な大文字小文字が含まれていない場合、これは効果的に次のようになります。
_string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;
_
これで、2つの不要な中間文字列が作成されました。ただし、コンパイラには特別なサポートがあるため、実際には上記を次のようにコンパイルできます。
_string x = string.Concat(a, b, c, d);
_
これにより、正確に正しい長さの単一の文字列を作成し、すべてのデータを1回だけコピーできます。いいね。
その理由は、+
を導入すると、C#演算子のバインディングルールが機能するためです。使用可能な+
演算子のセットを検討し、最適なオーバーロードを選択します。それらの演算子の1つは次のとおりです
string operator +(string x, object y)
このオーバーロードは、式null + true
の引数タイプと互換性があります。したがって、演算子として選択され、本質的に((string)null) + true
として評価され、値"True"
に評価されます。
C#言語仕様のセクション7.7.4には、この解決策に関する詳細が含まれています。
コンパイラーは、最初にnull引数を取ることができるoperator +()を探しに出かけます。どの標準値タイプも修飾されません。nullはそれらの有効な値ではありません。唯一の一致はSystem.String.operator +()であり、あいまいさはありません。
その演算子の2番目の引数も文字列です。それはkapooeyになり、boolを文字列に暗黙的に変換することはできません。
興味深いことに、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;
は実際にはコンパイラによって受け入れられません。
null
はnull文字列にキャストされ、boolからstringへの暗黙のコンバーターがあるため、true
はstringにキャストされ、次に+
演算子が適用されます:次のようになります:string str = "" + true.ToString();
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
この理由は利便性です(文字列の連結は一般的なタスクです)。
BoltClockが言ったように、「+」演算子は数値型、文字列で定義され、独自の型でも定義できます(演算子のオーバーロード)。
引数の型にオーバーロードされた「+」演算子がなく、それらが数値型でない場合、コンパイラーはデフォルトで文字列連結になります。
'+'を使用して連結すると、コンパイラはString.Concat(...)
の呼び出しを挿入し、Concatの実装は、渡された各オブジェクトに対してToStringを呼び出します。
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
.。