7

Path.GetTempFileName() メソッドを使用して一時ファイルを作成する TempFileStream クラスを定義したいとします。一時ファイルは、TempFileStream のオブジェクトが不要になったときに削除する必要があります。たとえば、閉じたり破棄したりします。

class TempFileStream: FileStream
{
  string m_TempFileName = Path.GetTempFileName();
  public TempFileStream(FileMode fileMode): base(m_TempFileName,fileMode) {}

  /// ...

 public ovverride Dispose(bool disposing)
 {
   /// ???
 }

}   

これを簡単かつ安全に実装するにはどうすればよいですか?

4

5 に答える 5

28

代わりにこれを試してください:

public class TempFileStream : FileStream
{
    public TempFileStream()
        : base(Path.GetTempFileName(), FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.DeleteOnClose) { }
    public TempFileStream(FileAccess access)
        : base(Path.GetTempFileName(), FileMode.Create, access, FileShare.Read, 4096, FileOptions.DeleteOnClose) { }
    public TempFileStream(FileAccess access, FileShare share)
        : base(Path.GetTempFileName(), FileMode.Create, access, share, 4096, FileOptions.DeleteOnClose) { }
    public TempFileStream(FileAccess access, FileShare share, int bufferSize)
        : base(Path.GetTempFileName(), FileMode.Create, access, share, bufferSize, FileOptions.DeleteOnClose) { }
}

FileOptions.DeleteOnClose オプションは、ファイルを閉じるときに、OS が一時ファイルを自動的に削除するようにします。すべてが処理されるため、特別な Dispose メソッドは必要ありません。

于 2010-06-28T16:52:51.237 に答える
5

これは面白いアイデアですが、このデザインには私を悩ませる何かがあります。設計ですでにこれに対処している場合は、ご容赦ください。しかし、あなたのデザインが の単純なラッパーFileStreamである場合、微妙ではありますが重大な問題があると思います。

ストリームが閉じているときにファイルを削除する場合、ファイル内のデータを実際に使用する唯一の方法FileAccessは、 ReadWrite. 正しい?つまり、次のようなコードでファイルを使用します。

using (TempFileStream t as new TempFileStream())
{
   WriteDataToTempFile(t);
   t.Seek(0, SeekOrigin.Begin);
   ReadDataFromTempFile(t);
}

私が見る問題はReadDataFromTempFile、ファイルが読み取り/書き込みアクセスではなく、読み取りアクセスで開かれることを期待していることです。そして、これは、見つけるのが非常に難しいと思われるいくつかのバグへの扉を開きます. 次のようなコードを検討してください。

using (TempFileStream t as new TempFileStream())
{
   MyClass o = new MyClass(o);
   o.TempStream = t;
   o.ProduceOutput();
   t.Seek(0, SeekOrigin.Begin);
   o.ProcessOutput();
}

...これと比較すると:

MyClass o = new MyClass();
string n = Path.GetTempFileName();
using (FileStream s = new FileStream(n, FileMode.Create, FileAccess.Write))
{
   o.TempStream = t;
   o.ProduceOutput();
}
using (FileStream s = new FileStream(n, FileMode.Open, FileAccess.Read))
{
   o.TempStream = t;
   o.ProcessOutput();
}
File.Delete(n);

確かに、最初の方法は 2 番目の方法よりも短いです。ただし、2 番目ProcessOutputのメソッドは、に書き込むメソッドを呼び出すと例外をスローしTempStreamます。(または、 set アクセサーが に書き込むメソッドへの呼び出しをディスパッチするイベント ハンドラーのイベントを発生させる set アクセサーを持つプロパティを設定しますTempStream。これにより、この問題が発生する可能性があります。) 最初のものは、明らかな理由もなく予期しない結果を生成します。

これを回避するには、クラスで基になるものを usingTempFileStreamで開く必要があると思います。次に、これを閉じて を使用する新しいメソッドを作成するメソッドを実装します。これを行うと、ファイルが読み取りアクセス用に開かれている間にファイルに書き込もうとするメソッド (またはその逆) は、少なくとも例外をスローします。FileStreamFileAccess.WriteRewindFileStreamFileAccess.Read

于 2009-10-05T18:21:58.893 に答える
3

これが古いスレッドであることは知っていますが、別の解決策があります。TempFileStream の実装を開始しましたが、より多くの同時実行が必要でした。私のユースケースには、MVC を介してデータベースの結果を CSV ファイルにエクスポートすることが含まれます。ダウンロードを開始する前に、潜在的に大きな一時ファイルが書き込まれるのを待つのではなく、データベース クエリからデータが利用可能になり次第、クライアントへのダウンロードを開始したいと考えています。

このソリューションでは、AnonymousPipeStream を満たす別のスレッドでクエリを起動します。次に、メイン スレッドは、パイプの反対側からデータを利用可能として丸呑みすることができます。.Net 4 タスクを使用します。

他の誰かがこれが役に立つことを願っています。

-ロブ

コントローラーの方法:

public FileResult ResultExport ( ReportOptions options )
{
    ResultExport rpt = new ResultExport( options );
    HttpContext.Response.BufferOutput = false;
    return File( rpt.Export(), "text/csv", "results.csv" );
}

モデル:

public ResultExport
{
    private AnonymousPipeServerStream WriteStream = null;

    public Stream Export()
    {
        //
        // We'll fire off the database query in a background
        // thread.  It will write data to one end of the pipe.  We'll return the reader
        // end of that pipe to our caller.
        //
        WriteStream = new AnonymousPipeServerStream( PipeDirection.Out );
        AnonymousPipeClientStream reader = new AnonymousPipeClientStream( PipeDirection.In, WriteStream.ClientSafePipeHandle );

        //
        // Call Execute() in a background thread.
        //
        Task.Factory.StartNew( () => Execute() );

        //
        // While Execute() is filling the pipe with data,
        // return the reader end of the pipe to our caller.
        //
        return reader;
    }

    private void Execute ()
    {
        //
        // WriteStream should only by populated by Export()
        //
        if( WriteStream != null )
        {
            using ( StreamWriter sw = new StreamWriter( WriteStream, Encoding.UTF8, 4096 ) )
            {
                //
                // Shove data into the StreamWriter as we get it from the database
                //
                foreach ( string line in ExportCore() )
                {
                    // Each line is a comma-delimited set of values
                    sw.WriteLine( line );
                }
            }
        }
    }
}
于 2011-09-07T12:58:01.990 に答える
2
base.Dispose(disposing); // disposes the base stream so the file is no longer used
if (disposing)
    File.Delete(m_TempFileName); // deletes the file

必要に応じて、File.Delete の適切な例外処理を追加する必要があります。

于 2009-10-05T11:16:36.553 に答える
0

基本的に、TempFileStreamロジックによれば、常に一意の名前で作成されたファイル(Path.GetTempFileNameが行うこと)を使用し、使用後に常に削除します。したがって、FileModeを常に同じモードで使用するため、FileModeを受け入れるコンストラクターを提供する必要はありません。

于 2009-10-05T11:22:23.007 に答える