以来はtrue
どのように、文字列型ではありませんnull + true
文字列?
string s = true; //Cannot implicitly convert type 'bool' to 'string'
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'
この背後にある理由は何ですか?
以来は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はオーバーロード可能な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;
string
C#コンパイラーに特別なケースがない場合、これは効果的に終了します。
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
が、この場合、文字列への変換に失敗すると、式全体がエラーになり、タイプがありません。
x
タイプが決まりますstring
。ここで使用される署名はstring operator+(string, object)
-にではbool
なくobject
(これで問題ありません)に変換されstring
ます。
その理由は、いったん導入する+
と、C#演算子バインディングルールが機能するようになるためです。+
利用可能な演算子のセットを考慮し、最適なオーバーロードを選択します。これらの演算子の1つは次のとおりです
string operator +(string x, object y)
このオーバーロードは、式の引数の型と互換性がありますnull + true
。したがって、それは演算子として選択され、本質的((string)null) + true
に評価されて値に評価されます"True"
。
C#言語仕様のセクション7.7.4には、この解決策に関する詳細が含まれています。
operator+
ためにstring
。代わりに、それはコンパイラーの頭の中にのみ存在し、それを単に呼び出しに変換しますstring.Concat
興味深いことに、生成されたものを検査するために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文字列にキャストされ、ブールから文字列への暗黙のコンバーター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
これの理由は便利です(文字列の連結は一般的なタスクです)。
BoltClockが言ったように、「+」演算子は数値型、文字列で定義され、独自の型でも定義できます(演算子のオーバーロード)。
引数の型にオーバーロードされた '+'演算子がなく、それらが数値型でない場合、コンパイラーはデフォルトで文字列連結を行います。
String.Concat(...)
'+'を使用して連結すると、コンパイラはへの呼び出しを挿入し、Concatの実装はそれに渡された各オブジェクトでToStringを呼び出します。