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