1. ホーム
  2. c#

[解決済み] null可能なbooleanに短絡演算子||や&&は存在するのか?RuntimeBinderはそう考えることがある

2023-07-03 01:32:09

質問

のC#言語仕様書を読みました。 条件付き論理演算子 ||&& は、短絡的な論理演算子としても知られています。私には、これらが nullable boolean のために存在するかどうかが不明確に思えました。 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);
  }
}

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

さて xy は驚くことではなく、それらの宣言によって両方のプロパティが取得され、結果の値も予想通りです。 xtrue であり ynull .

しかし xxA || B はバインディングタイム例外を発生させず、唯一のプロパティである A が読み込まれただけで B . なぜこのようなことが起こるのでしょうか?お分かりのように、私たちは B ゲッターをクレイジーなオブジェクトを返すように変更することができます。 "Hello world" のような、そして xx はまだ true と評価され、バインディングの問題は発生しません。

評価する A && Byy ) もバインディングタイムエラーを引き起こさない。そして、ここではもちろん両方のプロパティが取得されます。なぜこのようなことがランタイムバインダで許されるのでしょうか?もし B が "悪い" オブジェクトに変更された場合 (例えば string のような)悪いオブジェクトに変更された場合、バインディング例外が発生します。

これは正しい動作でしょうか? (どうして仕様から推測できるのでしょうか?)

もし、あなたが B を最初のオペランドとすると、両方とも B || AB && A は実行時バインダー例外を与える ( B | AB & A は、短絡しない演算子ですべてが正常であるため、うまく動作します。 |& ).

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

どのように解決するのですか?

まず最初に、非動的な nullable-bool のケースについて仕様が明確でないことを指摘していただきありがとうございます。将来のバージョンで修正するつもりです。コンパイラーの動作は、意図された動作です。 &&|| は nullable bools では動作しないことになっています。

しかし、ダイナミックバインダーはこの制限を実装していないようです。その代わり、コンポーネント操作を別々にバインドします。 & / | であり ?: . このため、最初のオペランドがたまたま true または false (の最初のオペランドとして許可されます。 ?: を使うことができます)、しかしもし null を最初のオペランドとして与えると (例えば B && A を試すと)、実行時バインディング例外が発生します。

考えてみれば、なぜ私たちが動的な &&|| このように、一つの大きな動的操作としてではなく、動的操作は実行時に結合されます。 オペランドが評価された後 そのため、結合はこれらの評価結果の実行時の型に基づくことができます。しかし、このような熱心な評価は、演算子を短絡させるという目的を破っています! そのため,代わりに生成される動的な &&|| は評価をバラバラに分割し、以下のように進めていきます。

  • 左のオペランドを評価する(この結果を x )
  • に変えてみてください。 bool に暗黙のうちに変換するか、あるいは true または false 演算子 (できない場合は失敗)
  • 使用方法 x の中の条件として ?: 操作
  • trueブランチでは x を結果的に
  • falseブランチで では は2番目のオペランドを評価します(その結果を y )
  • をバインドしてみてください。 & または | 演算子の実行時型に基づき xy (できない場合は失敗)
  • 選択した演算子を適用します

これは、ある種の "illegal" なオペランドの組み合わせを通過させる動作です。 ?: 演算子は、最初のオペランドをうまく NULL でない ブール値として扱われます。 & または | 演算子でうまく処理されると nullable ブール値として扱うことに成功し、2つの演算子が一致するかどうかを確認するために調整することはありません。

つまり、動的な && と || が nullable で動作するわけではありません。ただ、静的な場合と比較して、少し甘すぎる方法で実装されているだけです。これはおそらくバグとみなされるべきですが、私たちは決してこれを修正しないでしょう。また、動作を厳しくすることはほとんど誰の役にも立たないでしょう。

何が起こり、なぜそうなるのか、これで説明できたと思います! これは興味深い領域で、dynamic を実装したときに行った決定の結果について、私自身がしばしば困惑していることに気づきます。この質問はおいしかったですね、提起してくれてありがとう!

マッズ