C# Using Stream Flush

  • 396
  • 0
  • 2023-03-09

筆記下 Using 陳述式與 StreamWriter 空白的那些事

Using

using namespace 甚麼的就不贅述了

這邊主要描述 using 包物件的用法

目前 using 有兩種寫法

有大括弧包起來的跟直接分號結尾的

既有寫法 (大括弧)

void Test()
{
    using(var stream = new MemoryStream())
    {
        // do something...
    }
}

新增寫法 (分號)

void Test()
{
    using var stream = new MemoryStream();
    // do something...
}

差別是有大括弧包起來的有 scope 的功能

而分號寫法等同大括弧直接包到方法結尾

所以以上述例子中的寫法來說是等價的

StreamWriter

以下是將 Stream 寫入檔案的程式碼

var fileName = @"D:\file.bin";

using var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, leaveOpen:true))
{
    await writer.WriteAsync(fileName);
};

using (var file = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
    stream.WriteTo(file);
}

await File.ReadAllTextAsync(fileName).Dump();

以上會正確存到檔案並讀取後輸出內容 D:\file.bin 

此時如果把大括弧改成分號的寫法

var fileName = @"D:\file.bin";

using var stream = new MemoryStream();
using var writer = new StreamWriter(stream);
await writer.WriteAsync(fileName);

using var file = new FileStream(fileName, FileMode.Create, FileAccess.Write);
stream.WriteTo(file);

await File.ReadAllTextAsync(fileName).Dump();

首先會發生例外

The process cannot access the file 'D:\file.bin' because it is being used by another process.

需要在讀取檔案之前先關閉原本因為要寫入資料所打開的檔案

file.Close();

原本 using 結束時會自動觸發該方法

但是這邊我們把大括弧拿到之後 

 using 的結束位置變移動到了方法最後

導致在寫檔案的時候其實還沒有觸發

所以這邊需要手動呼叫 file.Close();

接著會發現檔案雖然有成功被建立

但是其內容為為空

這邊就必須提到 StreamWriterWriteAsync() 

並不會立即將資料寫入資料流中

實際寫入檔案是發生在 FlushAsync() 

跟上面一樣,這邊需要手動 FlushAsync()

所以修正完畢後大概是這樣

var fileName = @"D:\file.bin";

using var stream = new MemoryStream();
using var writer = new StreamWriter(stream);
await writer.WriteAsync(fileName);
await writer.FlushAsync();

using var file = new FileStream(fileName, FileMode.Create, FileAccess.Write);
stream.WriteTo(file);
file.Close();

await File.ReadAllTextAsync(fileName).Dump();

或加上大括弧來觸發 file.Close(); & FlushAsync

var fileName = @"D:\file.bin";

using var stream = new MemoryStream();
using (var writer = new StreamWriter(stream))
{
    await writer.WriteAsync(fileName);
};

using (var file = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
    stream.WriteTo(file);
};

await File.ReadAllTextAsync(fileName).Dump();

但上面這段程式會發生 例外 Cannot access a closed Stream

因為大括弧結束時 StreamWriter 會一併把變數 stream 釋放

導致雖然依舊可以使用 stream 變數

但其實已經被 dispose

這邊我們可以使用 leaveOpen:true 告訴 StreamWriter 保持 Stream Open

所以就會回到最上方的範例程式碼

var fileName = @"D:\file.bin";

using var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, leaveOpen:true))
{
    await writer.WriteAsync(fileName);
};

using (var file = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
    stream.WriteTo(file);
}

await File.ReadAllTextAsync(fileName).Dump();

另外我們也可以直接把 using 移除防止被釋放

反過來說就是要自己呼叫 Dispose()

補充

var fileName = @"D:\file.bin";

var utf8WithoutBom = new UTF8Encoding(false);

using var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, utf8WithoutBom, leaveOpen:true))
{
    await writer.WriteAsync(fileName);
};

using (var file = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
    stream.WriteTo(file);
}

await File.ReadAllTextAsync(fileName).Dump();

參照

using 陳述式 - C# 參考 | Microsoft Learn

關於 C# 的 using 陳述式在實務應用上的基本觀念 | The Will Will Web (miniasp.com)

Stream 類別 (System.IO) | Microsoft Learn

c# - 使用 StreamWriter 寫入 MemoryStream 傳回空 - 堆棧溢出 (stackoverflow.com)

PS5