[英]Dispose of a StreamWriter without declaring a variable in one line
以下 Powershell 命令復制整個文件失敗; 最后總是缺少一些字符。
[System.IO.StreamWriter]::new('C:\TEMP\b.csv', [System.Text.Encoding]::UTF8).Write([System.IO.StreamReader]::new('C:\Temp\a.csv', [System.Text.Encoding]::GetEncoding('iso-8859-1')).ReadToEnd())
我懷疑這是因為作者沒有刷新最后一位,因為這確實復制了整個文件:
$X = [System.IO.StreamReader]::new('C:\Temp\a.csv', [System.Text.Encoding]::GetEncoding('iso-8859-1'))
$Y = [System.IO.StreamWriter]::new('C:\TEMP\b.csv', [System.Text.Encoding]::UTF8)
$Y.Write($X.ReadAll())
$X.Dispose()
$Y.Dispose()
是否可以在沒有創建變量來引用它們的情況下處理(和刷新)讀取器和寫入器?
編輯:我嘗試了使用流讀取器/寫入器的單線器,希望讀取器的讀取緩沖區能夠直接傳輸到寫入器的寫入緩沖區,而不是等待讀取器將整個文件讀入 memory 然后寫入。 什么技術可以做到這一點?
我個人發現沒有聲明一次性 object 的代碼通常更簡潔/更簡潔,但我的重點是了解對象是否/如何處理自己,而不是樣式。
沒有必要避開變量或寫在一行上,但這種行為不是我所期望的。 在 VBA 中,可以像這樣復制文件並相信它會正確處理自己,而無需聲明變量並顯式刷新(我認為)。
Sub Cpy()
With New Scripting.FileSystemObject
.CreateTextFile("c:\Temp\Out.txt").Write .OpenTextFile("C:\Temp\In.txt", ForReading).ReadAll
End With
End Sub
通過在Class_Terminate()
過程中編寫適當的“清理”代碼,可以在自定義 VBA class 中實現類似的行為。 我假設一旦該行執行並且不再有與之關聯的變量,Streamwriter 將在終止時通過垃圾收集類似地刷新數據。
我還注意到該文件仍處於鎖定狀態,並且在關閉 powershell session 之前我無法刪除它。 有沒有辦法在沒有聲明要使用的變量的情況下刷新內容並釋放文件?
For the specific use case given, Santiago Squarzon's helpful answer is indeed the best solution: using the static methods of the static System.IO.File
obviates the need for instances representing files that require calling a .Close()
method or explicit disposing of .
要延遲讀取並因此支持逐行重疊讀取和寫入,您可以使用 static [System.IO.File]::ReadLines()
和[System.IO.File]::WriteAllLines()
方法,注意:WZriteAll butFile:WriteAll。這種方法 (a) 在 output 文件中總是使用平台原生[Environment]::NewLine
格式的換行符,而不管輸入文件使用什么換行符格式,並且 (b) 總是以這種格式添加一個尾隨換行符,即使輸入文件沒有尾隨換行符。
克服這些限制需要使用較低級別的原始字節 API, System.IO.FileStream
- 這再次需要顯式處理(參見底部)。
Given that your approach reads the entire input file into memory first and then writes, you could even make do with PowerShell cmdlets, assuming you're running PowerShell (Core) 7+ , which writes BOM-less UTF-8 files by default, and whose -Encoding
參數接受任何支持的編碼,例如您的情況下的 ISO-8859-1:
# PowerShell (Core) 7+ only Get-Content -Raw -Encoding iso-8859-1 C:\TEMP\a.csv | Set-Content -NoNewLine C:\TEMP\b.csv
至於你的一般問題:
從 PowerShell(核心)7.2.1 開始:
PowerShell沒有與 C# 的using
語句等效的構造,該語句允許自動處理類型實現System.IDisposable
接口的對象(在文件 I/O API 的情況下,隱式關閉文件)。
GitHub issue #9886討論了添加這樣的語句,但討論表明它可能不會實現。
注意:雖然 PowerShell 確實有一系列以關鍵字using
開頭的語句,但它們有不同的用途 - 請參閱概念about_Using幫助主題。
未來的 PowerShell 版本clean {... }
cleanup {... }
在高級 function或腳本終止時自動調用的 clean {...}(或 cleanup {...})塊,這允許執行任何必要的函數腳本級清理(處理對象) - 參見RFC #294 。
是否從終結器調用.Dispose()
方法取決於實現IDisposable
接口的每種類型。 只有這樣,垃圾收集器才會最終自動處理掉 object。
For System.IO.StreamWriter
and also the lower-level System.IO.FileStream
class, this appears not to be the case, so in PowerShell you must call .Close()
or .Dispose()
explicitly , which is best done from the finally
塊try
/ catch
/ finally
語句。
您可以通過結合object構造和變量賦值的各個方面來減少儀式,但是一個健壯的習語仍然需要很多儀式:
$x = $y = $null
try {
($y = [System.IO.StreamWriter]::new('C:\TEMP\b.csv', [System.Text.Encoding]::UTF8)).
Write(
($x = [System.IO.StreamReader]::new('C:\Temp\a.csv', [System.Text.Encoding]::GetEncoding('iso-8859-1'))).
ReadToEnd()
)
} finally {
if ($x) { $x.Dispose() }
if ($y) { $y.Dispose() }
}
一個助手 function, Use-Object
(下面的源代碼)可以稍微緩解這個問題:
Use-Object
($x = [System.IO.StreamReader]::new('C:\Temp\a.csv',[System.Text.Encoding]::GetEncoding('iso-8859-1'))),
($y = [System.IO.StreamWriter]::new('C:\TEMP\b.csv', [System.Text.Encoding]::UTF8)) `
{ $y.Write($x.ReadToEnd()) }
Use-Object
源代碼:
function Use-Object {
param(
[Parameter(Mandatory)] [array] $ObjectsToDispose,
[Parameter(Mandatory)] [scriptblock] $ScriptBlock
)
try {
. $ScriptBlock
} finally {
foreach ($o in $ObjectsToDispose) {
if ($o -is [System.IDisposable]) {
$o.Dispose()
}
}
}
}
只是為了向您展示這是可能的,並且更容易做到,使用System.IO.File
、 WriteAllText()
和ReadAllText()
的 static 方法。
以下查詢https://loripsum.net/ API 以獲取隨機段落並使用iso-8859-1
編碼寫入文件。 然后讀取該文件並使用相同的編碼寫入副本,最后比較兩個文件哈希。 正如你所看到的,閱讀和寫作都是單線完成的。
可以刪除using
語句,但您需要使用完整類型名稱。
將位置設置為臨時文件夾以進行測試。
using namespace System.IO
using namespace System.Text
$fileRead = [Path]::Combine($pwd.Path, 'test.txt')
$fileWrite = [Path]::Combine($pwd.Path, 'test-copy.txt')
$loremIpsum = Invoke-RestMethod 'https://loripsum.net/api/5/short/headers/plaintext'
[File]::WriteAllText($fileWrite, $loremIpsum, [Encoding]::GetEncoding('iso-8859-1'))
[File]::WriteAllText(
$fileWrite,
[File]::ReadAllText($fileRead, [Encoding]::GetEncoding('iso-8859-1')),
[Encoding]::GetEncoding('iso-8859-1')
)
(Get-FileHash $fileRead).Hash -eq
(Get-FileHash $fileWrite).Hash # => Should be True
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.