2020/4/25 追記
C# 8.0以降はusingステートメントをネストしないで利用できます。こちらを検討するもの良さそうですね。
C# 8.0の新機能のusing 宣言をご参考ください。
前置き
久しぶりに他人のC#のコードを見て思い出しました。10年以上C#のコードを読んでいますが複数の連続するusingステートメント(以下、using)は都度ネストしていることが非常に多いです。
using (var srTextA = new StreamReader("a.txt"))
{
// ネストした
using (var srTextB = new StreamReader("b.txt"))
{
// またネストした
using (var swTextC = new StreamWriter("c.txt"))
{
// またまたネストした
}
}
}
すでに色々なところで"まとめ書き"(この表現が適切か分かりません。)のノウハウは公開されていると思うのですが、筆者の関わるシステムの9割以上は都度ネストしているusingで記述されています。「筆者の考えが少数派」なのか「関わるシステムのくじ運」の問題か分かりませんが都度ネストしてしまうと可読性が低くなると思っています。そこで改めてusingの"まとめ書き"に特化して記事を投稿し願わくば"まとめ書き"を広めたいと思いました。
少しボヤきますが、コーディング規約でifのネストの数が3以上になる場合はコーディング方法を見直すこと
と規定しても、usingのネストの数がそれ以上なら本質を見失っていると思うのです。
リファクタリング
都度ネストしている元コード
2つのテキストファイルの内容を統合して新しいテキストファイルに書き出すサンプルプログラムです。
サンプルの質は申し訳ないです
var documentFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
using (var srTextA = new StreamReader($@"{documentFolderPath}\a.txt"))
{
// ネストした
using (var srTextB = new StreamReader($@"{documentFolderPath}\b.txt"))
{
// またネストした
using (var swTextC = new StreamWriter($@"{documentFolderPath}\c.txt"))
{
// またまたネストした
swTextC.WriteLine(srTextA.ReadToEnd());
swTextC.WriteLine(srTextB.ReadToEnd());
}
}
}
案1) 重ねてまとめる
usingを重ねて1つのブロックで構成します。
var documentFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
// usingを重ねる
using (var srTextA = new StreamReader($@"{documentFolderPath}\a.txt"))
using (var srTextB = new StreamReader($@"{documentFolderPath}\b.txt"))
using (var swTextC = new StreamWriter($@"{documentFolderPath}\c.txt"))
{
// ネストした
swTextC.WriteLine(srTextA.ReadToEnd());
swTextC.WriteLine(srTextB.ReadToEnd());
}
案2) 変数をまとめて宣言する
同じ型の変数宣言は1つにまとめられるのでsrTextAとsrTextBは同時に宣言します。型推論は利用できません。swTextCは案1
の方法で重ねてまとめます。
var documentFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
// StreamReaderの変数をまとめる。
using (StreamReader srTextA = new StreamReader($@"{documentFolderPath}\a.txt"),
srTextB = new StreamReader($@"{documentFolderPath}\b.txt"))
using (var swTextC = new StreamWriter($@"{documentFolderPath}\c.txt")) // こちらは案1と同じで重ねただけ。
{
swTextC.WriteLine(srTextA.ReadToEnd());
swTextC.WriteLine(srTextB.ReadToEnd());
}
個人的には型推論が好きなのとリファクタリングのし易さから案1
が好みです。さらに正直に申しますと案2
の方法はQiitaに投稿する際に改めてC#の言語仕様を読んで初めて知りました。
言語仕様から見るusingの"まとめ書き"
usingの"まとめ書き"が広まれば目的達成ですが、折角なので他のステートメントで"まとめ書き"の概念を比較してみます。C#の言語仕様でusingを見てみます。
using_statement
: 'using' '(' resource_acquisition ')' embedded_statement
もうひとつ比較としてifも見てみます。
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
両者ともembedded_statement(埋め込みステートメント)
であり同じ位置付けです。usingに限っては前述のとおり可読性を向上の主張をしているつもりです。しかし、この提案をifで実装すると不自然になります。
都度ネストしている元コード
2つのリストの内容を統合して新しいリストに格納するサンプルプログラムです。
サンプルの質は・・・申し訳ないです
あくまで「言語仕様から見るusingの"まとめ書き"」でusingとifのネストを比較説明するためにifの条件を3行に分けました。1行の条件式で書くべきですが無理やり条件式を3行に分けて書くならばネスト自体に違和感無いと思います。
if (listA.Any())
{
if (listB.Any())
{
if (!listC.Any())
{
listC.AddRange(listA);
listC.AddRange(listB);
}
}
}
こちらのパターンもあると思います。
if (listA.Any())
if (listB.Any())
if (!listC.Any())
{
listC.AddRange(listA);
listC.AddRange(listB);
}
usingで提案したように重ねてまとめる
usingでは可読性が上がると思い"まとめ書き"を提案しましたが、ifでは可読性は下がると思います。筆者はifの"まとめ書き"は推していません。elseを書くことになったら更に可読性は下がるでしょう。しつこいようですが「1行の条件式で書くべき」なのは承知しています。あくまで他ステートメントで比較をしました。
if (listA.Any())
if (listB.Any())
if (!listC.Any())
{
listC.AddRange(listA);
listC.AddRange(listB);
}
VSでコード整形
実際にVS2015のデフォルト設定のまま「ドキュメントのフォーマット」(コード整形)を行うと以下になります。
if (listA.Any())
if (listB.Any())
if (!listC.Any())
{
listC.AddRange(listA);
listC.AddRange(listB);
}
using (var srTextA = new StreamReader($@"{documentFolderPath}\a.txt"))
using (var srTextB = new StreamReader($@"{documentFolderPath}\b.txt"))
using (var swTextC = new StreamWriter($@"{documentFolderPath}\c.txt"))
{
swTextC.WriteLine(srTextA.ReadToEnd());
swTextC.WriteLine(srTextB.ReadToEnd());
}
筆者はなるべくデフォルトの設定を好みますので、VSが「ifはインデントするけれどusingはインデントしない」ことは「VSにお墨付きを頂いた」と勝手に思っています
※もちろんif以外にもインデントされるステートメントはありますね。
OSSのコード状況
自身の周りでは都度ネストされているusingが多いのですが、自身の周り以外の状況を調査すべくOSSのコードをみました。Json.NETやNLogのソースコードでは"まとめ書き"が行われており、筆者の考えは「多数派であった」か、少数派だとしても「十分な事例がある」と主張できそうです。
終わりに
まだQiitaが無かったころブログに掲載されているサンプルコードでも、都度ネストしているusingを非常に良く見かけました。最近のサンプルコードでは少なくなってきているとは思いますが、筆者の周りの実働システムでは都度ネストしているusingが非常に多かったので記事にさせて頂きました。また、言語仕様を見るきっかけにもなり情報をアウトプットすると副作用で勉強になることがあり良かったです。