DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

ファイルをロック(アクセスを制限)する

ファイルを開いているときに、そのファイルの内容が他のプロセスから勝手に変更されては困るというケースは多いでしょう。FileShare列挙体を指定してFileStreamクラスでファイルを開けば、そのファイルへのアクセスをどのように制限するか(読み込みのみを制限するか、書き込みのみを制限するか、両方制限するか、何も制限しないか)を変更できます。この制限は、Closeメソッドでファイルを閉じるまで有効です。

なお、FileStreamでファイルを開く方法については、「ファイルをバイト型配列に読み込む、バイト型配列をファイルに書き込む」で詳しく説明していますので、そちらをご覧ください。

以下の例では、ファイルを閉じるまで、書き込み、読み込みすべてのアクセスを禁止しています。このような方法でファイルを開いている最中に別のプロセス(または自分のプロセス)がファイルを開こうとすると、例外System.IO.IOExceptionが発生します。

VB.NET
コードを隠すコードを選択
'読み込むファイルの名前
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()
C#
コードを隠すコードを選択
//読み込むファイルの名前
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で開いたファイルをロックしない

文字コードを指定してテキストファイルを読み込む」で紹介したようにStreamReaderを使ってファイルを開いたとき、開いているファイルは別のプロセスから読み込むはできますが、書き込みはできません。これを読み込みも書き込みも制限しないようにするには、FileShare.ReadWriteを指定してFileStreamオブジェクトを作成します。このFileStreamオブジェクトを基にしてStreamReaderオブジェクトを作成すれば、StreamReaderを使用してファイルを読み込むことができます。

VB.NET
コードを隠すコードを選択
'読み込むファイルの名前
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()
C#
コードを隠すコードを選択
//読み込むファイルの名前
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で読み込みができなくなることを確認できます。

VB.NET
コードを隠すコードを選択
'バッファを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
C#
コードを隠すコードを選択
//バッファを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バイトになります。

Microsoft Officeなどが開いているファイルを開く

WordやExcelなどで現在開いているファイルをFileStreamで読み込もうとすると、例外System.IO.IOException(「別のプロセスで使用されているため、プロセスはファイル '(ファイルのパス)' にアクセスできません。」)が発生します。これを回避するには、FileShare.ReadWriteを指定してファイルを開きます。

Microsoft Office以外でもこの方法が使えるかもしれませんが、アプリケーションがどのようにファイルをロックしているかによりますので、当然使えないこともあります。お手数ですが、ご自分でご確認ください。(コメントでご報告をいただけると、ありがたいです。)

  • 履歴:
  • 2010/1/20 タイトルを変更。
  • 2010/1/27 「StreamReaderで開いたファイルをロックしない」「ファイルの一部をロックする」「Microsoft Officeなどが開いているファイルを開く」を追加。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。