簡體   English   中英

處理 StreamWriter 而不在一行中聲明變量

[英]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 finallytry / 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.FileWriteAllText()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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM