繁体   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