Roslynはコードのコンパイルに失敗しました


95

プロジェクトをVS2013からVS2015に移行した後、プロジェクトはビルドされなくなりました。次のLINQステートメントでコンパイルエラーが発生します。

static void Main(string[] args)
{
    decimal a, b;
    IEnumerable<dynamic> array = new string[] { "10", "20", "30" };
    var result = (from v in array
                  where decimal.TryParse(v, out a) && decimal.TryParse("15", out b) && a <= b // Error here
                  orderby decimal.Parse(v)
                  select v).ToArray();
}

コンパイラはエラーを返します:

エラーCS0165未割り当てのローカル変数 'b'の使用

この問題の原因は何ですか?コンパイラの設定で修正できますか?


11
@BinaryWorrier:なぜですか?パラメータbを介して割り当てた後にのみ使用しoutます。
Jon Skeet

1
VS 2015のドキュメントでは、「out引数として渡される変数は、渡される前に初期化する必要はありませんが、呼び出されたメソッドは、メソッドが戻る前に値を割り当てる必要があります。」そのため、これはバグのように見えます。そのtryParseによって初期化されることが保証されています。
Rup

3
エラーに関係なく、このコードはout引数について悪いことすべてを例示しました。それはTryParsenull許容値(または同等のもの)を返しますか?
Konrad Rudolph

1
@KonradRudolphのほうwhere (a = decimal.TryParse(v)).HasValue && (b = decimal.TryParse(v)).HasValue && a <= bずっと見栄え良い
ローリング、

2
ただし、これをに簡略化できますdecimal a, b; var q = decimal.TryParse((dynamic)"10", out a) && decimal.TryParse("15", out b) && a <= b;。私はこれを上げるロズリンのバグオープンしました
ローリング、2015

回答:


112

この問題の原因は何ですか?

コンパイラのバグのようです。少なくともそうでした。が、decimal.TryParse(v, out a)そしてdecimal.TryParse(v, out b)式が動的に評価され、私は期待コンパイラはまだ理解して、それが到達した時点でa <= b、両方ab確実に割り当てられています。動的型付けで思い付く奇妙さがあるとしても、a <= b両方のTryParse呼び出しを評価した後でしか評価しないと思います。

しかし、それはオペレータと変換トリッキーを通じて、それを表現するために持っている完全に実現可能だということが判明したA && B && C評価ACではなくB、あなたしている狡猾なのに十分な場合-を。Neal Gafterの独創的な例については、Roslynバグレポートを参照してください。

その処理をdynamicさらに困難にする-オペランドが動的である場合に関連するセマンティクスを説明することは困難です。これは、オーバーロードの解決を実行するために、オペランドを評価して関連する型を見つける必要があるため、直観に反する場合があります。ただし、ここでもニールは、コンパイラエラーが必要であることを示す例を考え出しました。これはバグではなく、バグ修正です。それを証明するためのニールへの莫大な賞賛。

コンパイラの設定で修正できますか?

いいえ。ただし、エラーを回避する代替手段があります。

最初に、動的になるのを止めることができます。文字列のみを使用することがわかっている場合は、範囲変数のタイプを使用IEnumerable<string> または指定できます(つまり)。それが私の好みのオプションです。vstringfrom string v in array

あなたがいる場合、本当にダイナミックにそれを維持する必要があり、ちょうど与えるbと開始する値:

decimal a, b = 0m;

これは害を及ぼすことはありません- 実際の動的評価では何もおかしくないことがわかっているためb、使用する前に値を割り当ててしまい、初期値は無関係になります。

さらに、括弧の追加も機能するようです:

where decimal.TryParse(v, out a) && (decimal.TryParse("15", out b) && a <= b)

これにより、さまざまなオーバーロード解決がトリガーされるポイントが変わり、たまたまコンパイラーが幸せになります。

まだ残っている問題が1つあります。2つのオペランドを持つ「通常の」実装で演算子が使用されている&&場合にのみ適用されることを明記するには、演算子による明確な割り当てに関する仕様のルールを明確にする必要があります。これが次のECMA標準で修正されることを確認してみます。&&bool


うん!IEnumerable<string>ブラケットの適用または追加がうまくいきました。コンパイラはエラーなしでビルドされます。
ramil89 2015

1
を使用decimal a, b = 0m;するとエラーが削除される可能性がありますが、out値はまだ計算されていないため、a <= b常にを使用0mします。
Paw Baltzersen 2015

12
@PawBaltzersen:どうしてあなたはそれを考えさせるのですか?それは、常にます比較の前に割り当てること-それはコンパイラが何らかの理由(バグ、基本的には)のために、それを証明することができないだけということです。
Jon Skeet

1
副作用のない解析メソッドを持っている、すなわち。decimal? TryParseDecimal(string txt)解決策になるかもしれません
zahir 2015

1
遅延初期化なのでしょうか。「最初のものが当てはまる場合、b割り当てられていない可能性があることを意味する2番目の値を評価する必要がない」と考えています。私はそれが無効な推論であることは知っていますが、括弧がそれを修正する理由を説明しています...
durron597


16

バグレポートで一生懸命勉強したので、自分で説明しようと思います。


想像Tへの暗黙的なキャストを持ついくつかのユーザー定義型であるboolとのことで交互falsetrue、始まりますfalse。コンパイラーが知る限り、dynamic最初の引数の最初の引数は&&その型に評価される可能性があるため、悲観的である必要があります。

次に、コードをコンパイルすると、これが発生する可能性があります。

  • 動的バインダーが最初のを評価するとき、&&次のことを行います。
    • 最初の引数を評価する
    • これはT-暗黙的ににキャストされboolます。
    • あ、falseそうなので、2番目の引数を評価する必要はありません。
    • &&評価の結果を最初の引数にします。(いいえ、false何らかの理由で、ではありません。)
  • 動的バインダーが2番目のを評価するとき、&&次のことを行います。
    • 最初の引数を評価します。
    • これはT-暗黙的ににキャストされboolます。
    • おお、それtrueはなので、2番目の引数を評価します。
    • ...がらくた、b割り当てられていません。

つまり、変数が「確実に割り当てられている」か「確実に割り当てられていない」かだけでなく、「falseステートメントの後に確実に割り当てられている」か「確実に割り当てられている」かを示す特別な「明確な割り当て」ルールがあります。trueステートメントの後に割り当てられます。」

これらは存在し&&、and ||(およびand !および??and ?:)を処理するときに、コンパイラーは、変数が複雑なブール式の特定のブランチに割り当てられるかどうかを調べることができます。

ただし、これらは、式のタイプがブール値のままである間のみ機能ます。式の一部がdynamic(または非ブール型の静的型)である場合、式が次のtrueいずれであるかを確実に言うことができなくなります。false次に実行boolする分岐を決定するためにキャストするときに、考えが変わった可能性があります。


更新:これは解決文書化れました

以前のコンパイラーによって動的式に実装された明確な割り当て規則により、コードが確実に割り当てられない変数が読み取られる可能性があるコードのケースが許可されていました。この1つのレポートについては、https://github.com/dotnet/roslyn/issues/4509を参照してください

...

この可能性があるため、valに初期値がない場合、コンパイラーはこのプログラムのコンパイルを許可してはなりません。以前のバージョンのコンパイラ(VS2015より前)では、valに初期値がない場合でも、このプログラムをコンパイルできました。Roslynは、初期化されていない可能性のある変数を読み取るこの試みを診断します。


1
他のマシンでVS2013を使用して、実際にこれを使用して未割り当てメモリを読み取ることができました。それはそれほどエキサイティングではありません:(
Rawling

単純なデリゲートで初期化されていない変数を読み取ることができます。outを持つメソッドに到達するデリゲートを作成しますref。それは喜んでそれを行い、値を変更することなく、変数を割り当てます。
IllidanS4はモニカに2015

好奇心から、そのスニペットをC#v4でテストしました。しかし、好奇心から、コンパイラは暗黙のキャスト演算子とは対照的に演算子false/ をどのように使用するかを決定しますtrueか?ローカルでimplicit operator boolは、最初の引数を呼び出し、次に2番目のオペランドを呼び出しoperator false、最初のオペランドを呼び出し、その後implicit operator bool最初のオペランドを再度呼び出します。これは私には意味がありません、最初のオペランドは基本的にブール値に1回煮詰める必要がありますか?
ロブ

@ロブこれはdynamic、連鎖&&ケースですか?基本的にそれが行くのを見てきました(1)最初の引数を評価します(2)暗黙のキャストを使用して短絡できるかどうかを確認します(3)できないので、2番目の引数を回避します(4)両方のタイプがわかったので、最もよく見ることができ&&、ユーザー定義である&(5)呼び出し演算子false私は(6)私ができる(ので、短絡をできるかどうかを確認するには、最初の引数にfalseし、implicit bool結果は最初の引数である...そして次のように反対します)&&、(7)暗黙のキャストを使用して、(もう一度)短絡できるかどうかを確認します。
ローリング、2015

@ IllidanS4おもしろそうですが、どうすればいいのかわかりません。スニペットをください。
2015

15

これはバグではありません。このフォームの動的式がこのようなout変数を未割り当てのままにする方法の例については、https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713を参照してください


1
私の回答は受け入れられ、非常に賛成されているため、解決策を示すために編集しました。これに関するあなたのすべての仕事をありがとう-私に私の間違いを説明することを含めて:)
Jon Skeet
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.