Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9749

変数スコープを誤解していた件

$
0
0

自己紹介

昔は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を表示させます。

クラスAとメソッドM
classA{publicstringX;}voidM(Aa){if(a.Xisnot{}x)return;// null だったら early return。// x を使って何か処理をする。Debug.WriteLine($"x: {x}");//ここでエラーが出ていない!}

VS上でこのコードを書いてもエラーが検出されないので、この時点で"x"がifブロックの外で参照できる事、私が変数スコープを誤解していた件が確定っ!

早くもタイトルを回収してしまいましたが、でもまぁ折角コードを書いたので気を取り直して上記を呼び出してみます。

メソッドMの呼び出し側
//XがNULLAa=new();//Viva! C#9.0M(a);//early returnが効いて、何も出力されない//Xを埋めるa.X="hoge";M(a);//"x: hoge"が出力される

...と、想定通り(※)の挙動になりました。

※私のではなく、元記事の想定の通り...

なぜ誤解したか...?

ぱっと見の印象

for文
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"に対応した当時に読んだ内容と同じかどうかは不明ですが...

マイクロソフトのドキュメント中、

(引用開始)

C#
expristypevarname

expr が 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が自分のうっかり記事というのもどうかと思いましたが、同じように誤解している方もいるかもしれないと思い、書かせて頂きました。参考になれば幸いです!


Viewing all articles
Browse latest Browse all 9749

Trending Articles