オペレーターを短絡させてください|| および&&はnull許容ブール値に存在しますか?RuntimeBinderは時々そう考えます


84

条件付き論理演算子 ||&&、短絡論理演算子としても知られるC#言語仕様を読みました。私には、これらがnull許容ブール値、つまりオペランド型Nullable<bool>(これも記述されているbool?)に存在するかどうかが不明であるように思われたので、非動的型付けで試してみました。

bool a = true;
bool? b = null;
bool? xxxx = b || a;  // compile-time error, || can't be applied to these types

これで問題は解決したようです(仕様を明確に理解できませんでしたが、Visual C#コンパイラの実装が正しいと仮定すると、今ではわかりました)。

しかし、私dynamicもバインディングを試してみたかったのです。だから私は代わりにこれを試しました:

static class Program
{
  static dynamic A
  {
    get
    {
      Console.WriteLine("'A' evaluated");
      return true;
    }
  }
  static dynamic B
  {
    get
    {
      Console.WriteLine("'B' evaluated");
      return null;
    }
  }

  static void Main()
  {
    dynamic x = A | B;
    Console.WriteLine((object)x);
    dynamic y = A & B;
    Console.WriteLine((object)y);

    dynamic xx = A || B;
    Console.WriteLine((object)xx);
    dynamic yy = A && B;
    Console.WriteLine((object)yy);
  }
}

驚くべき結果は、これが例外なく実行されることです。

まあ、xそしてy驚くことではありませんが、それらの宣言により両方のプロパティが取得され、結果の値は期待どおりであり、xistrueおよびyisnullです。

ただし、の評価でxxA || Bバインディング時間の例外は発生せず、プロパティのみAが読み取られ、は読み取られませんでしたB。なぜこれが起こるのですか?あなたが言うことができるように、私たちは変更される可能性Bのような、狂気のオブジェクトを返すようにゲッターを"Hello world"、そしてxxまだに評価されますtrue結合の問題もなく...

評価中 A && B(for yy)をも、バインディング時間エラーは発生しません。もちろん、ここでは両方のプロパティが取得されます。なぜこれがランタイムバインダーによって許可されるのですか?から返さBれたオブジェクトが(のようなstring)「不良」オブジェクトに変更された場合、バインディング例外が発生します。

これは正しい動作ですか?(仕様からどのように推測できますか?)

あなたはしようとした場合B、両方の、最初のオペランドとしてB || A及びB && A(例外バインダーランタイムを与えるB | Aと、B & A、すべてが非短絡事業者と通常であるとして、細かい作業を|して&)。

(Visual Studio 2013のC#コンパイラ、およびランタイムバージョン.NET 4.5.2で試してみました。)


4
Nullable<Boolean>関与するインスタンスはまったくなく、ボックス化されたブール値のみが扱われdynamicます-でのテストbool?は関係ありません。(もちろん、これは完全な答えではなく、1つの胚芽だけです。)
Jeroen Mostert 2014

3
A || Bあなたは評価したくないという点で、意味のある一定量になるB場合を除きA、それはないです偽が、あります。ですから、実際には、式のタイプを知ることはできません。A && Bバージョンはもっと驚くべきことである-私はスペックで見つけることができるかがわかります。
Jon Skeet 2014

2
@JeroenMostert:コンパイラはの種類があればことを決定した場合を除きまあ、Aあるboolの価値がBあるnull、その後、bool && bool?オペレータが関与している可能性があります。
ジョンスキート2014

4
興味深いことに、これによりコンパイラまたは仕様のバグが明らかになったようです。代わりに&&それを解決することについて話しているC#5.0仕様&は、特に両方のオペランドがbool?-である場合を含みますが、それが参照する次のセクションはnull許容の場合を処理しません。それについてさらに詳しく説明するような答えを追加することはできますが、それを完全に説明することはできません。
Jon Skeet 2014

14
スペックの問題についてMadsにメールを送り、それが私がそれをどのように読んでいるかの問題であるかどうかを確認しました...
Jon Skeet 2014

回答:


67

まず第一に、非動的なnullable-boolの場合の仕様が明確ではないことを指摘してくれてありがとう。将来のバージョンで修正する予定です。コンパイラの動作は意図された動作です。&&そして||NULL可能boolsに動作するようになっていません。

ただし、動的バインダーはこの制限を実装していないようです。代わりに、コンポーネント操作を個別にバインドします:&/|?:。したがって、最初のオペランドがたまたまtrueor false(ブール値であるための最初のオペランドとして許可されている?:)であるかどうかを混乱nullさせることができますが、最初のオペランドとして指定した場合(たとえば、B && A上記の例で)は、ランタイムバインディング例外を取得します。

考えてみると、動的操作を1つの大きな動的操作としてではなく&&||この方法で実装した理由がわかります。動的操作は、オペランドが評価された後、実行時バインドされるため、結果の実行時タイプに基づいてバインドできます。それらの評価の。しかし、そのような熱心な評価は、オペレーターを短絡させる目的を打ち破ります!だからではなく、ダイナミック用のコード生成&&||粉々に壊れ評価アップし、次のように進行します。

  • 左側のオペランドを評価します(結果を呼び出しましょう) x
  • bool暗黙の変換を介してそれを変換してみてください、またはtrueまたはfalse演算子に(できない場合は失敗します)
  • xの条件として使用?:操作
  • 真のブランチでは、 x、結果として
  • 偽のブランチでは、今、第二オペランド(レッツ・コールの結果を評価しますy
  • andの実行時型に基づいて&or|演算子をバインドしてみてください(できない場合は失敗します)xy
  • 選択した演算子を適用します

これは、オペランドの特定の「違法な」組み合わせを通過させる動作です。?:演算子は最初のオペランドをnull許容でないブール値として正常に処理し、&or|演算子はそれをnull許容ブール値として正常に処理し、2つは一致することを確認するために調整することはありません。 。

したがって、それほど動的ではありません&&および|| null許容型に取り組んでいます。静的な場合と比較して、それらがたまたま少し寛大すぎる方法で実装されているだけです。これはおそらくバグと見なされるべきですが、それは重大な変更になるため、修正することはありません。また、それは誰もが行動を引き締めるのを助けることはほとんどありません。

うまくいけば、これは何が起こるのか、そしてその理由を説明しています!これは興味深い分野であり、ダイナミックを実装したときに行った決定の結果に戸惑うことがよくあります。この質問は美味しかったです-それを持ってきてくれてありがとう!

マッド


動的バインディングでは、短絡した場合に第2オペランドのタイプを実際に知ることができないため、これらの短絡演算子は特別であることがわかります。多分スペックはそれを言及するべきですか?もちろん、a内のすべてdynamicがボックス化されてbool?いるHasValueため、aと「simple」の違いはわかりませんbool
Jeppe Stig Nielsen 2014

6

これは正しい動作ですか?

はい、確かにそうです。

仕様からそれをどのように推測できますか?

C#仕様バージョン5.0のセクション7.12には、条件演算子&&||、動的バインディングがそれらにどのように関連しているかに関する情報があります。関連するセクション:

条件付き論理演算子のオペランドのコンパイル時型が動的である場合、式は動的にバインドされます(§7.2.2)。この場合、式のコンパイル時型は動的であり、以下で説明する解決は、コンパイル時型が動的であるオペランドの実行時型を使用して実行時に行われます

これがあなたの質問に答える重要なポイントだと思います。実行時に発生する解決策は何ですか?セクション7.12.2、ユーザー定義の条件付き論理演算子の説明:

  • 操作x && yはT.false(x)?として評価されます。x:T。&(x、y)、ここでT.false(x)はTで宣言された演算子falseの呼び出しであり、T。&(x、y)は選択された演算子&の呼び出しです。
  • 操作x || yはT.true(x)として評価されますか?x:T。|(x、y)、ここでT.true(x)はTで宣言された演算子trueの呼び出しであり、T。|(x、y)は選択された演算子|の呼び出しです。

どちらの場合も、最初のオペランドxはfalseortrue演算子を使用してブール値に変換されます。次に、適切な論理演算子が呼び出されます。これを念頭に置いて、残りの質問に答えるのに十分な情報があります。

しかし、Aのxxの評価|| Bはバインディング時間の例外を引き起こさず、プロパティAのみが読み取られ、Bは読み取られませんでした。なぜこれが発生するのですか?

||オペレーターにとって、それが続くことはわかっていますtrue(A) ? A : |(A, B)。短絡しているので、拘束時間の例外は発生しません。場合でもAだったfalse、我々は考えまだため、指定された解決手順の、実行時バインディング例外を取得できません。場合Afalse、我々は、やる|に成功節7.11.4ごとに、ヌル値を処理できるオペレーターを、。

A && B(yyの場合)を評価しても、バインディング時間エラーは発生しません。もちろん、ここでは両方のプロパティが取得されます。なぜこれがランタイムバインダーによって許可されるのですか?Bから返されたオブジェクトが「不良」オブジェクト(文字列など)に変更された場合、バインディング例外が発生します。

同様の理由で、これも機能します。&&として評価されfalse(x) ? x : &(x, y)ます。Aに正常に変換できるboolため、問題はありません。Bがnullであるため、&演算子はaboolをとる演算子からをとる演算子に持ち上げられます(セクション7.3.7)。bool?パラメータ実行時の例外はありません。

両方の条件演算子で、Bがbool(またはnullダイナミック)以外の場合、boolとnon-boolをパラメーターとして受け取るオーバーロードが見つからないため、ランタイムバインディングは失敗します。ただし、これAは、演算子の最初の条件(truefor ||falsefor &&)を満たさない場合にのみ発生します。これが発生する理由は、動的バインディングが非常に遅延しているためです。Afalseでない限り、論理演算子をバインドしようとはせず、論理演算子を評価するためにそのパスをたどる必要があります。いったんAオペレータのための最初の条件を満たしていない、それが結合例外で失敗します。

Bを第1オペランドとして試してみると、両方のB || AとB && Aは、ランタイムバインダーの例外を示します。

うまくいけば、今では、なぜこれが起こるのかをすでに知っているでしょう(または私は悪い仕事をして説明しました)。この条件演算子を解決する最初のステップは、論理演算を処理する前に、最初のオペランド、を取りB、ブール変換演算子(false(B)またはtrue(B))の1つを使用することです。もちろん、Bbeingnulltrueまたはfalseに変換できないため、ランタイムバインディング例外が発生します。


dynamicバインディングでは、コンパイル時の型(最初の引用)ではなく、インスタンスの実際の型を使用して実行時に発生するのは当然のことです。あなたの第二の引用はここに過負荷がないタイプので、無関係であるoperator trueoperator falseexplicit operator帰国bool以外の何かであるoperator truefalseA && B(私の例では)許可する方法で仕様を読み取ることは困難です。a && bただし、aおよびbが静的に型付けされたnull許容ブール値、つまりbool? abool? bコンパイル時にバインドされる場所も許可しません。しかし、それは許可されていません。
Jeppe Stig Nielsen 2014

-1

Nullable型は条件付き論理演算子を定義しません|| および&&。次のコードをお勧めします:

bool a = true;
bool? b = null;

bool? xxxxOR = (b.HasValue == true) ? (b.Value || a) : a;
bool? xxxxAND = (b.HasValue == true) ? (b.Value && a) : false;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.