自己紹介
昔はDelphi、ここ数年はC#で業務アプリを開発しているarimooです。
もうLINQとリフレクションのない世界には戻れそうにありません。
Qiita初投稿、Advent Calendar初参加です。よろしくお願い致します。
本記事は「C# その2 Advent Calendar 2020」19日目です。
発端
いつも参考にさせて頂いている「未確認飛行C」の記事C# の null 判定の話を拝見していて(記事の趣旨とは無関係な部分で)気になるコード例が...
(引用開始)
voidM(Aa){if(a.Xisnot{}x)return;// null だったら early return。// x を使って何か処理をする。// ここでは x に非 null な値が入っているはず。}(引用終了)
あれっ!? if文カッコ内の"x"のスコープってifブロックで閉じてなかったっけ...?
検証
※環境はVisualStudio2019、ターゲットフレームワークを.NET 5.0にしたWinFormsアプリで検証しています。
という訳で適当なクラスAを作成し、記事中の"x を使って何か処理をする。"にDebug.WriteLineでxを表示させます。
classA{publicstringX;}voidM(Aa){if(a.Xisnot{}x)return;// null だったら early return。// x を使って何か処理をする。Debug.WriteLine($"x: {x}");//ここでエラーが出ていない!}VS上でこのコードを書いてもエラーが検出されないので、この時点で"x"がifブロックの外で参照できる事、私が変数スコープを誤解していた件が確定っ!
早くもタイトルを回収してしまいましたが、でもまぁ折角コードを書いたので気を取り直して上記を呼び出してみます。
//XがNULLAa=new();//Viva! C#9.0M(a);//early returnが効いて、何も出力されない//Xを埋めるa.X="hoge";M(a);//"x: hoge"が出力される...と、想定通り(※)の挙動になりました。
※私のではなく、元記事の想定の通り...
なぜ誤解したか...?
ぱっと見の印象
for(inti=0;i<10;i++){}Debug.WriteLine($"{i}");//iはスコープ外、ダメstring[]strs=newstring[]{"aaa","bbb","ccc"};foreach(varsinstrs){}Debug.WriteLine($"{s}");//sはスコープ外、ダメ上記for/foreach文やメソッドの引数のように、{}ブロック外で使えないという印象が強く効いていました。
マイクロソフトのドキュメントの誤読
"expr is type varname"に対応した当時に読んだ内容と同じかどうかは不明ですが...
(引用開始)
expristypevarnameexpr が true であり is が if ステートメントに使用されている場合は、varname は if ステートメント内のみに割り当てられます。 varname のスコープは、is 式から if ステートメントを閉じるブロックの末尾までになります。他の任意の場所に varname を使用すると、割り当てられていない変数の使用によるコンパイル時エラーが生成されます。
(引用終了)
...の赤字部分を見て早とちりして、その先の青字をさらっと流してしまっていました。
冷静に考えるとスコープ外なら"現在のコンテキストにvarnameは存在しません"になるはずですね...
※とはいえ"varname のスコープは、is 式から if ステートメントを閉じるブロックの末尾までになります。"はダウトかな~。
"割り当てられていない変数の使用によるコンパイル時エラー"という事は、割り当てればエラーにならないハズ! ...という事で、検証した内容をコメント文に記載しています。
(最初の検証で上手く行く事は判っていますが、実際に弄ってみるとフロー解析の賢さを体験できます!)
strings1=null;if(s1isstringw1){//ここに入る場合、w1はs1の中身で埋まっているw1+=":TRUE";//埋まっているから+=もOK}else{//ここに入る場合、w1は未割当の状態w1="FALSE";//ここでw1への代入で、WriteLineでも使える状態になる//w1 += "FALSE"; //先にこれはダメ。w1は"未割当"エラーになる//return; //returnするならWriteLineまで行かないからw1埋めなくてもOK!}Debug.WriteLine($"{w1}");//elseブロックをコメントアウトすると、w1は"未割当"エラーになるw1をifブロック外で使うためにはelse側で埋めてあげるかreturnする必要がある、という事ですね!
(is notにするとifブロック側で埋めるかreturnする必要があります)
まとめ
便利に使っているif (obj is Hoge h)構文のhが、ifブロックの外側でも使えるとは目から鱗でした!
※今のところ、元記事にあるような演算子オーバーロード回避の需要が発生しない限り使わないとは思いますが...(^^;
(そもそも演算子オーバーロード自体を余り使っていない...)
初投稿&初Advent Calendarが自分のうっかり記事というのもどうかと思いましたが、同じように誤解している方もいるかもしれないと思い、書かせて頂きました。参考になれば幸いです!