次のクラスを検討してください。
public class A {
public B GetB() {
Console.WriteLine("GetB");
return new B();
}
}
public class B {
[System.Diagnostics.Conditional("DEBUG")]
public void Hello() {
Console.WriteLine("Hello");
}
}
さて、メソッドをこの方法で呼び出す場合:
var a = new A();
var b = a.GetB();
b.Hello();
リリースビルド(つまり、DEBUG
フラグなし)では、Hello()
の呼び出しがコンパイラによって省略されるため、コンソールにGetB
のみが表示されます。デバッグビルドでは、両方の出力が表示されます。
次に、メソッド呼び出しをチェーンしましょう:
a.GetB().Hello();
デバッグビルドの動作は変更されていません。ただし、フラグが設定されていない場合は、異なる結果が得られます。both呼び出しは省略され、コンソールに印刷は表示されません。 ILを簡単に見ると、行全体がコンパイルされていなかったことがわかります。
C#の最新のECMA標準 (ECMA-334、つまりC#5.0)によると、Conditional
属性がメソッドに配置されたときの予想される動作は次のとおりです(エンファシスマイニング):
関連する条件付きコンパイルシンボルの1つ以上が呼び出しポイントで定義されている場合、条件付きメソッドの呼び出しが含まれます。それ以外の場合呼び出しは省略されます。 (§22.5.3)
これは、チェーン全体を無視する必要があることを示していないようです。したがって、私の質問です。とはいえ、 MicrosoftのC#6.0ドラフト仕様 にはもう少し詳細があります。
シンボルが定義されている場合、呼び出しが含まれます。それ以外の場合、呼び出し(受信側の評価と呼び出しのパラメーターを含む)は省略されます。
呼び出しのパラメーターが評価されないという事実は、関数本体で#if
ディレクティブではなくこの機能を使用する理由の1つであるため、十分に文書化されています。ただし、「受信者の評価」に関する部分は新しいものです。他の場所で見つけることはできないようで、上記の動作を説明しているようです。
これに照らして、私の質問は:この状況でC#コンパイラが評価しないa.GetB()
理由は何ですか?条件付き呼び出しの受信者が一時変数に格納されているかどうかに基づいて異なる動作をしますか?
少し掘り下げてみると、 C#5.0言語仕様 には実際にセクションに2番目の引用符が既に含まれていることがわかりました17.4.2条件属性424ページ。
Marc Gravellの答え はすでに、この動作が意図されていることと実際に意味することを示しています。また、この背後にある根拠についても質問しましたが、Marcのオーバーヘッドの除去については不満のようです。
たぶん、あなたはなぜそれが除去できるオーバーヘッドと考えられているのだろうか?
a.GetB().Hello();
が省略されてシナリオでまったく呼び出されないHello()
は、額面では奇妙に見えるかもしれません。
私はこの決定の背後にある理論的根拠を知りませんが、私自身の推論をもっともらしく見つけました。たぶんあなたにも役立つでしょう。
メソッドチェーン は、前の各メソッドに戻り値がある場合にのみ可能です。これは、これらの値、つまりa.GetFoos().MakeBars().AnnounceBars();
を使用して何かをしたい場合に意味があります。
値を返さずに何かをdoesするだけの関数がある場合、その後ろに何かを連鎖することはできませんが、条件付きの場合のように、メソッドチェーンの最後に置くことができますメソッドは、戻り値の型がvoidでなければならないためです。
また、前のメソッド呼び出しのresultは、throwawayを取得します。したがって、a.GetB().Hello();
の例では、GetB()
の結果には、このステートメントが実行された後に生きる理由がありません。基本的に、あなたは暗黙的にGetB()
の結果が必要なのはHello()
を使用する場合だけです。
Hello()
が省略されている場合、なぜGetB()
にする必要があるのですか? Hello()
を省略すると、行は割り当てられずにa.GetB();
になり、多くのツールは戻り値を使用しないことを警告します。
これで大丈夫でないように見える理由は、メソッドが特定の値を返すために必要なことをしようとしているだけでなく、 副作用 、つまりI/Oも持っているからです。代わりに 純粋な関数 を使用した場合、後続の呼び出しを省略した場合、つまり、次のような場合、GetB()
にreally理由はありません結果に対して何もしません。
GetB()
の結果を変数に割り当てた場合、これはそれ自体のステートメントであり、とにかく実行されます。この理由は
var b = a.GetB();
b.Hello();
Hello()
への呼び出しのみが省略され、メソッドチェーンを使用する場合はチェーン全体が省略されます。
より完全な視点を得るために、まったく異なる場所を探すこともできます。C#6.0で導入された null-conditional operator または elvis operator?
nullチェックを使用したより複雑な式の構文シュガーだけですが、nullチェックに基づいて短絡するオプションを使用して、メソッドチェーンのようなものを構築できます。
例えば。 GetFoos()?.MakeBars()?.AnnounceBars();
は、前のメソッドがnull
を返さない場合にのみ最後に到達します。そうでない場合、後続の呼び出しは省略されます。
直観に反するかもしれませんが、シナリオをこれの逆と考えてみてください。チェーンの終わりに到達していないので、コンパイラはHello()
チェーンのa.GetB().Hello();
の前の呼び出しを省略します。
これはすべてアームチェアの推論でしたので、これとエルビス演算子との類似性を一粒の塩で取ってください。
次のフレーズになります。
(受信者の評価と呼び出しのパラメーターを含む)は省略されます。
式では:
a.GetB().Hello();
「受信者の評価」はa.GetB()
です。そのため:仕様によるは省略され、[Conditional]
が使用されていないのオーバーヘッドを回避できる便利なトリックです。ローカルに配置する場合:
var b = a.GetB();
b.Hello();
「レシーバの評価」はローカルb
だけですが、元のvar b = a.GetB();
はまだ評価されます(ローカルb
が削除された場合でも)。
このcanは意図しない結果になるため、[Conditional]
を慎重に使用してください。しかし、その理由は、ロギングやデバッグなどを簡単に追加および削除できるようにするためです。パラメーターは、単純に処理するとalsoが問題になる可能性があることに注意してください。
LogStatus("added: " + engine.DoImportantStuff());
そして:
var count = engine.DoImportantStuff();
LogStatus("added: " + count);
LogStatus
が[Conditional]
とマークされている場合は、veryと異なる場合があります。その結果、実際の「重要なもの」が完了しませんでした。
条件付き呼び出しの受信者が一時変数に格納されているかどうかに基づいて、実際に異なる動作をする必要がありますか?
はい。
この状況で
a.GetB()
を評価しないC#コンパイラの背後にある理由は何ですか?
MarcとSørenからの答えは基本的に正しいです。この答えは、タイムラインを明確に文書化することです。