ファイルを開いているときに、そのファイルの内容が他のプロセスから勝手に変更されては困るというケースは多いでしょう。FileShare列挙体を指定してFileStreamクラスでファイルを開けば、そのファイルへのアクセスをどのように制限するか(読み込みのみを制限するか、書き込みのみを制限するか、両方制限するか、何も制限しないか)を変更できます。この制限は、Closeメソッドでファイルを閉じるまで有効です。
なお、FileStreamでファイルを開く方法については、「ファイルをバイト型配列に読み込む、バイト型配列をファイルに書き込む」で詳しく説明していますので、そちらをご覧ください。
以下の例では、ファイルを閉じるまで、書き込み、読み込みすべてのアクセスを禁止しています。このような方法でファイルを開いている最中に別のプロセス(または自分のプロセス)がファイルを開こうとすると、例外System.IO.IOExceptionが発生します。
'読み込むファイルの名前 Dim fileName As String = "C:\test.txt" 'ファイルを開く Dim fs As New System.IO.FileStream(fileName, _ System.IO.FileMode.Open, _ System.IO.FileAccess.Read, _ System.IO.FileShare.None) 'ファイルを読み込むバイト型配列を作成する Dim bs(fs.Length - 1) As Byte 'ファイルの内容をすべて読み込む fs.Read(bs, 0, bs.Length) '閉じる fs.Close()
//読み込むファイルの名前 string fileName = @"C:\test.txt"; //ファイルを開く System.IO.FileStream fs = new System.IO.FileStream( fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.None); //ファイルを読み込むバイト型配列を作成する byte[] bs = new byte[fs.Length]; //ファイルの内容をすべて読み込む fs.Read(bs, 0, bs.Length); //閉じる fs.Close();
以下にFileShare構造体のメンバとその意味を表にまとめます。
FileShareのメンバ | 説明 |
---|---|
None | ファイルを閉じるまで、別プロセスやこのプロセスからの読み込み、書き込みを禁止する。 |
Read | ファイルを閉じるまで、別プロセスやこのプロセスからの書き込みを禁止し、読み込みは許可する。 |
ReadWrite | 別プロセスやこのプロセスからの読み込み、書き込みを許可する。 |
Write | ファイルを閉じるまで、別プロセスやこのプロセスからの読み込みを禁止し、書き込みは許可する。 |
Delete | 削除を許可する。Windows 9x系では失敗する。.NET Framework 2.0で追加。 |
Inheritable | 子プロセスで継承できるようにする。 |
「文字コードを指定してテキストファイルを読み込む」で紹介したようにStreamReaderを使ってファイルを開いたとき、開いているファイルは別のプロセスから読み込むはできますが、書き込みはできません。これを読み込みも書き込みも制限しないようにするには、FileShare.ReadWriteを指定してFileStreamオブジェクトを作成します。このFileStreamオブジェクトを基にしてStreamReaderオブジェクトを作成すれば、StreamReaderを使用してファイルを読み込むことができます。
'読み込むファイルの名前 Dim fileName As String = "C:\test.txt" 'ファイルを開く Dim fs As New System.IO.FileStream(fileName, _ System.IO.FileMode.Open, _ System.IO.FileAccess.Read, _ System.IO.FileShare.ReadWrite) 'FileStreamを基にしたStringReaderのインスタンスを作成 Dim enc As System.Text.Encoding = _ System.Text.Encoding.GetEncoding("shift_jis") Dim sr As New System.IO.StreamReader(fs, enc) 'ファイルの内容をすべて読み込む Dim s As String = sr.ReadToEnd() '閉じる 'srを閉じれば、基になるfsも閉じられる sr.Close()
//読み込むファイルの名前 string fileName = @"C:\test.txt"; //ファイルを開く System.IO.FileStream fs = new System.IO.FileStream( fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); //FileStreamを基にしたStringReaderのインスタンスを作成 System.Text.Encoding enc = System.Text.Encoding.GetEncoding("shift_jis"); System.IO.StreamReader sr = new System.IO.StreamReader(fs, enc); //ファイルの内容をすべて読み込む string s = sr.ReadToEnd(); //閉じる //srを閉じれば、基になるfsも閉じられる sr.Close();
StreamWriterクラスでファイルを書き込むときも全く同じ方法で、FileShareを変更したFileStreamを基にしてStreamWriterオブジェクトを作成することができます。
FileStreamで開いているファイルの一部をロックする方法として、FileStream.Lockメソッドが用意されています。アンロックするときは、FileStream.Unlockメソッドを呼び出します。
Lockメソッドでファイルの一部をロックしたとき、別のプロセスからはロックした部分以外からの読み込みはできても、ロックした部分から読み込もうとすると例外IOExceptionがスローされます。(自分のプロセスであっても、別のFileStreamであれば同様です。)
Lockメソッドでロックされていない部分をロックすることはできますが、すでにロックされている部分をロックしようとすると、一部が重なるだけであっても、IOExceptionがスローされます。
Unlockメソッドで指定する位置と長さは、Lockメソッドで指定した位置と長さと完全に一致する必要があります。もし違っていた場合は、IOExceptionがスローされます。
下のサンプルは、Lockメソッドでファイルをロックするテストを行うものです。フォームにボタンが4つ配置してあるとして、Button1でファイルの一部をロックし、Button2でアンロックし、Button3でロックする範囲にないデータを読み込み、Button4でロックする範囲にあるデータを読み込んでいます。このアプリケーションを2つ起動し、一方でButton1をクリックしてファイルをロックすると、もう一方ではButton4で読み込みができなくなることを確認できます。
'バッファを10バイトにしてFileStreamオブジェクトを作成 Private testStream As New System.IO.FileStream("C:\test.txt", _ System.IO.FileMode.Open, _ System.IO.FileAccess.Read, _ System.IO.FileShare.ReadWrite, _ 10) 'Button1のClickイベントハンドラ Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Try '11バイト目から20バイトロックする testStream.Lock(10, 20) MessageBox.Show("Lockしました") Catch ex As System.IO.IOException MessageBox.Show(ex.Message) End Try End Sub 'Button2のClickイベントハンドラ Private Sub Button2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button2.Click Try 'アンロックする testStream.Unlock(10, 20) MessageBox.Show("Unlockしました") Catch ex As System.IO.IOException MessageBox.Show(ex.Message) End Try End Sub 'Button3のClickイベントハンドラ Private Sub Button3_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button3.Click Try '先頭から1バイトを読み込む 'ただしバッファが10バイトなので、10バイト読み込まれる 'ロックされていない範囲にある testStream.Seek(0, System.IO.SeekOrigin.Begin) MessageBox.Show(testStream.ReadByte().ToString()) Catch ex As System.IO.IOException MessageBox.Show(ex.Message) End Try End Sub 'Button4のClickイベントハンドラ Private Sub Button4_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button4.Click Try '30バイト目から1バイトを読み込む 'ロックされている位置にある testStream.Seek(29, System.IO.SeekOrigin.Begin) MessageBox.Show(testStream.ReadByte().ToString()) Catch ex As System.IO.IOException MessageBox.Show(ex.Message) End Try End Sub 'フォームが閉じたとき Protected Overrides Sub OnFormClosed( _ ByVal e As System.Windows.Forms.FormClosedEventArgs) testStream.Close() MyBase.OnFormClosed(e) End Sub '.NET Framework 1.1以前 'Protected Overrides Sub OnClosed(ByVal e As System.EventArgs) ' testStream.Close() ' MyBase.OnClosed(e) 'End Sub
//バッファを10バイトにしてFileStreamオブジェクトを作成 private System.IO.FileStream testStream = new System.IO.FileStream( @"C:\test.txt", System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite, 10); //Button1のClickイベントハンドラ private void Button1_Click(object sender, EventArgs e) { try { //11バイト目から20バイトロックする testStream.Lock(10, 20); MessageBox.Show("Lockしました"); } catch (System.IO.IOException ex) { MessageBox.Show(ex.Message); } } //Button2のClickイベントハンドラ private void Button2_Click(object sender, EventArgs e) { try { //アンロックする testStream.Unlock(10, 20); MessageBox.Show("Unlockしました"); } catch (System.IO.IOException ex) { MessageBox.Show(ex.Message); } } //Button3のClickイベントハンドラ private void Button3_Click(object sender, EventArgs e) { try { //先頭から1バイトを読み込む //ただしバッファが10バイトなので、10バイト読み込まれる //ロックされていない範囲にある testStream.Seek(0, System.IO.SeekOrigin.Begin); MessageBox.Show(testStream.ReadByte().ToString()); } catch (System.IO.IOException ex) { MessageBox.Show(ex.Message); } } //Button4のClickイベントハンドラ private void Button4_Click(object sender, EventArgs e) { try { //30バイト目から1バイトを読み込む //ロックされている位置にある testStream.Seek(29, System.IO.SeekOrigin.Begin); MessageBox.Show(testStream.ReadByte().ToString()); } catch (System.IO.IOException ex) { MessageBox.Show(ex.Message); } } //フォームが閉じたとき protected override void OnFormClosed(FormClosedEventArgs e) { testStream.Close(); base.OnFormClosed(e); } //.NET Framework 1.1以前 //protected override void OnClosed(EventArgs e) //{ // testStream.Close(); // base.OnClosed(e); //}
この例ではFileStreamのバッファを10バイトにしていますが、この指定を省略すると、Button1でファイルをロックしている時、Button3でファイルを読み込めなくなります。これは、FileStreamのバッファが10バイトより大きくなり、ロックされている範囲のデータまで読み込もうとするためです。ちなみにFileStreamのバッファのサイズは8バイト未満にすることができず、8未満の値を指定したときは8バイトになります。
WordやExcelなどで現在開いているファイルをFileStreamで読み込もうとすると、例外System.IO.IOException(「別のプロセスで使用されているため、プロセスはファイル '(ファイルのパス)' にアクセスできません。」)が発生します。これを回避するには、FileShare.ReadWriteを指定してファイルを開きます。
Microsoft Office以外でもこの方法が使えるかもしれませんが、アプリケーションがどのようにファイルをロックしているかによりますので、当然使えないこともあります。お手数ですが、ご自分でご確認ください。(コメントでご報告をいただけると、ありがたいです。)