こんにちは。いつも業務自動化の最前線で戦っている皆さん、お疲れ様です。
かつては「マクロの記録」からプログラミングの楽しさを知った方も、VB.NETという強力な武器を手にすると、必ずと言っていいほどぶつかる「壁」があります。
それは、「他のプロセス(Excelやシステム)が掴んでいるファイルをどうやって読み書きするか」という問題です。
「ファイルは存在するのに、開こうとすると『別のプロセスが使用中のため、プロセスはファイルにアクセスできません』と怒られてしまう……」
このエラーを前にして、`Thread.Sleep`で待機したり、手動でファイルを閉じたりしていませんか? 実は、.NET Frameworkが提供する`System.IO.FileStream`と`FileShare`を正しく理解すれば、この問題はエレガントに解決できます。
今日は、一歩先のエンジニアへ脱皮するための「ファイルアクセス排他制御」の本質を、優しく深く解説していきましょう。
—
1. なぜ「File.ReadAllText」では不十分なのか?
初心者の頃によく使う`File.ReadAllText`や`File.ReadAllLines`は、非常に便利なメソッドです。しかし、これらは内部的に「ファイルに対して非常に厳格な態度」を取ります。
これら簡易メソッドは、暗黙的に「自分だけがこのファイルを読み書きする(他人は触らせない)」という設定でファイルを開こうとします。そのため、もしExcelがそのCSVを開いていたり、別のシステムがログを書き込んでいたりすると、たとえ読み込みたいだけでもアクセスを拒否されてしまうのです。
ここで重要になるのが、「FileStream」というクラスと、「FileShare」という設定オプションです。
—
2. FileShare:共存のための「お作法」
`FileStream`を生成する際、第4引数に指定できる`FileShare`列挙体こそが、今回の主役です。これは「自分がファイルを開いている間、他人がそのファイルに対して何をすることを許すか」を宣言するものです。
| 設定値 | 意味(他プロセスへの許可) |
| :— | :— |
| FileShare.None | 誰も何もさせない(独占)。標準的な動作。 |
| FileShare.Read | 他の人が「読むこと」だけは許可する。 |
| FileShare.Write | 他の人が「書くこと」だけは許可する。 |
| FileShare.ReadWrite | 他の人が「読み書きすること」を全て許可する。 |
実務で最も役立つのは `FileShare.ReadWrite` です。
「他の誰かが今まさに書き込んでいる最中であっても、私はその隙間から中身を覗かせてもらいますよ」という、非常に柔軟なスタンスを取ることができるようになります。
—
3. 【実践レシピ】ロックされたファイルを安全に読み込む
では、実際にExcelが開いている最中のCSVファイルを、エラーを出さずに読み込むコードを見てみましょう。
.net
Imports System.IO
Imports System.Text
Public Module FileAccessMaster
”’
”’
”’ 対象ファイルのパス
”’
Public Function ReadFileEvenIfLocked(filePath As String) As String
Dim content As String = String.Empty
Try
‘ FileStreamを手動で構成するのが「プロ」の第一歩です。
‘ 第1引数: ファイルパス
‘ 第2引数: Open(既存のファイルを開く)
‘ 第3引数: Read(自分は読み込み専用で開く)
‘ 第4引数: ReadWrite(【重要】他人が読み書きしていても許可する)
Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
‘ StreamRenderingには、メモリ効率の良いStreamReaderを使います。
‘ エンコーディング(Shift-JIS等)は、現場の環境に合わせて指定してください。
Using reader As New StreamReader(fs, Encoding.GetEncoding(“shift_jis”))
content = reader.ReadToEnd()
End Using
End Using
Catch ex As IOException
‘ ファイル自体が存在しない、あるいは物理的にアクセス不可能な場合のハンドリング
Console.WriteLine($”ファイルアクセスエラー: {ex.Message}”)
Throw
End Try
Return content
End Function
End Module
このコードの「ここ」が凄い!
1. `Using`ブロックの徹底: `FileStream`はOSのリソース(ファイルハンドル)を直接消費します。`Using`を使うことで、読み終わった瞬間に確実にハンドルを解放し、他の処理に道を譲ります。
2. `FileShare.ReadWrite`の指定: これにより、他プロセスがファイルをロックしていても、OSレベルで「共有読み取り」が許可されます。
3. `StreamReader`との連携: `FileStream`で開いた「通り道」を、`StreamReader`という「翻訳者」に渡してテキスト化する。この役割分担が.NETの美しい流儀です。
—
4. 逆に「自分が書き込んでいる間」を守るには?
逆に、自分のプログラムがログを出力している最中に、中途半端な状態で他人に中身を読まれたくない(または書き換えられたくない)場合は、`FileShare.None`を明示的に指定します。
.net
‘ 自分だけが書き込み、他人は一切のアクセスを禁止する
Using fs As New FileStream(logPath, FileMode.Append, FileAccess.Write, FileShare.None)
‘ 書き込み処理…
End Using
このように、「自分がどう振る舞うか(`FileAccess`)」と「他人にどう振る舞ってほしいか(`FileShare`)」をセットで考えることが、堅牢なシステムへの第一歩です。
—
5. 陥りやすい罠:ファイルが「完全に書き込み中」の場合
`FileShare.ReadWrite`を使っても解決できないケースが稀にあります。それは、相手のプロセス(書き込み側)が「一切の共有を許可しない(`FileShare.None`)」でファイルを開いている場合です。
この場合は、残念ながらOSレベルで完全にシャットアウトされているため、相手が閉じるのを待つしかありません。実務的には、数回リトライするループ処理を組むのが一般的です。
.net
‘ 簡易的なリトライロジックのイメージ
Dim retryCount As Integer = 0
While retryCount < 3
Try
Return ReadFileEvenIfLocked(path)
Catch
retryCount += 1
System.Threading.Thread.Sleep(1000) ' 1秒待って再挑戦
End Try
End While
---
終わりに:オブジェクトの背後にある「意思」を感じよう
Visual Basicの世界は、ただ命令を並べるだけではなく、こうした「OSとの対話」を意識し始めると一気に視界が開けます。
`FileStream`を使いこなし、`FileShare`を適切に設定できるようになれば、あなたはもう「マクロの延長」でコードを書いている初心者ではありません。システムの挙動を予測し、競合を未然に防ぐ「アーキテクト」への道を歩み始めています。
今回の知識を武器に、ぜひ現場の「ファイルが開けなくて止まる」という不具合を、スマートに解決してあげてください。応援しています。
—
執筆者:チーフアーキテクト
「コードは書くものではなく、紡ぐもの。リソースの解放こそが、エンジニアの慈悲である。」
