简体   繁体   English

处理 StreamWriter 而不在一行中声明变量

[英]Dispose of a StreamWriter without declaring a variable in one line

The following Powershell command fails to copy the entire file;以下 Powershell 命令复制整个文件失败; a few characters are always missing from the end.最后总是缺少一些字符。

[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())

I suspect it's because the writer doesn't flush the last bits because this does copy the entire file:我怀疑这是因为作者没有刷新最后一位,因为这确实复制了整个文件:

$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()

Is it possible to dispose of (and flush) the reader & writer without having created variables to reference them?是否可以在没有创建变量来引用它们的情况下处理(和刷新)读取器和写入器?

EDIT: I tried this one-liner using streamreader/writer hoping the reader's read buffer would directly transfer to the writer's write buffer rather than waiting for the reader to read the entire file into memory and then write.编辑:我尝试了使用流读取器/写入器的单线器,希望读取器的读取缓冲区能够直接传输到写入器的写入缓冲区,而不是等待读取器将整个文件读入 memory 然后写入。 What technique might achieve that?什么技术可以做到这一点?

I personally find code that does not declare a single-use object to often be cleaner / more succinct, but my focus is on understanding whether/how objects dispose of themselves, not the style.我个人发现没有声明一次性 object 的代码通常更简洁/更简洁,但我的重点是了解对象是否/如何处理自己,而不是样式。

There's no need to eschew variables or write on one line, but this behaviour isn't what I expected.没有必要避开变量或写在一行上,但这种行为不是我所期望的。 In VBA one can copy a file like so and trust it will dispose of itself properly without having to declare a variable and explicitly flush (I think).在 VBA 中,可以像这样复制文件并相信它会正确处理自己,而无需声明变量并显式刷新(我认为)。

Sub Cpy()
With New Scripting.FileSystemObject
    .CreateTextFile("c:\Temp\Out.txt").Write .OpenTextFile("C:\Temp\In.txt", ForReading).ReadAll
End With
End Sub

One can achieve similar behaviour in a custom VBA class by writing appropriate 'clean-up' code in a Class_Terminate() procedure.通过在Class_Terminate()过程中编写适当的“清理”代码,可以在自定义 VBA class 中实现类似的行为。 I assumed the Streamwriter would similarly flush data upon termination via garbage collection once the line executes and there's no longer a variable associated with it.我假设一旦该行执行并且不再有与之关联的变量,Streamwriter 将在终止时通过垃圾收集类似地刷新数据。

I also noticed that the file remains locked and I cannot delete it until I close the powershell session.我还注意到该文件仍处于锁定状态,并且在关闭 powershell session 之前我无法删除它。 Is there a way to flush contents and release the file without having declared a variable to work with?有没有办法在没有声明要使用的变量的情况下刷新内容并释放文件?

  • 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 class obviates the need for instances representing files that require calling a .Close() method or explicit disposing of. 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 .

    • To read lazily and therefore support overlapping reading and writing, line by line , you can use the static [System.IO.File]::ReadLines() and [System.IO.File]::WriteAllLines() methods, but note that this approach (a) invariably uses platform-native [Environment]::NewLine -format newlines in the output file, irrespective of what newline format the input file uses, and (b) invariably adds a trailing newline in this format, even if the input file had no trailing newline.延迟读取并因此支持逐行重叠读取和写入,您可以使用 static [System.IO.File]::ReadLines()[System.IO.File]::WriteAllLines()方法,注意:WZriteAll butFile:WriteAll。这种方法 (a) 在 output 文件中总是使用平台原生[Environment]::NewLine格式的换行符,而不管输入文件使用什么换行符格式,并且 (b) 总是以这种格式添加一个尾随换行符,即使输入文件没有尾随换行符。

    • Overcoming these limitations would require use of a lower-level, raw-byte API, System.IO.FileStream - which again requires explicit disposal (see bottom section).克服这些限制需要使用较低级别的原始字节 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 parameter accepts any supported encoding, such as ISO-8859-1 in your case: 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

As for your general question :至于你的一般问题

As of PowerShell (Core) 7.2.1:从 PowerShell(核心)7.2.1 开始:

  • PowerShell has no construct equivalent to C#'s using statement that allows automatic disposing of objects whose type implements the System.IDisposable interface (which, in the case of file I/O APIs, implicitly closes the files). PowerShell没有与 C# 的using语句等效的构造,该语句允许自动处理类型实现System.IDisposable接口的对象(在文件 I/O API 的情况下,隐式关闭文件)。

    • GitHub issue #9886 discusses adding such a statement, but the discussion suggests that it likely won't be implemented. GitHub issue #9886讨论了添加这样的语句,但讨论表明它可能不会实现。

    • Note: While PowerShell does have a family of statements starting with keyword using , they serve different purposes - see the conceptual about_Using help topic.注意:虽然 PowerShell 确实有一系列以关键字using开头的语句,但它们有不同的用途 - 请参阅概念about_Using帮助主题。

  • A future PowerShell version will support a clean {... } (or cleanup {... } ) block that is automatically called when an advanced function or script terminates, which allows performing any necessary function-script-level cleanup (disposing of objects) - see RFC #294 .未来的 PowerShell 版本clean {... } cleanup {... }高级 function或脚本终止时自动调用的 clean {...}(或 cleanup {...})块,这允许执行任何必要的函数脚本级清理(处理对象) - 参见RFC #294

It is up to each type implementing the IDisposable interface whether it calls the .Dispose() methods from the finalizer .是否从终结器调用.Dispose()方法取决于实现IDisposable接口的每种类型。 Only if so is an object automatically disposed of eventually , by the garbage collector .只有这样,垃圾收集器才会最终自动处理掉 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 block of a try / catch / finally statement . 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语句

You can cut down on the ceremony somewhat by combining the aspects of object construction and variable assignment, but a robust idiom still requires a lot of ceremony:您可以通过结合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() }
}

A helper function, Use-Object (source code below) can alleviate this a bit:一个助手 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 source code : 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()
      }
    }
  }
  
}

Just to show you that this is possible, and easier to do, using the static methods of System.IO.File , WriteAllText() and ReadAllText() .只是为了向您展示这是可能的,并且更容易做到,使用System.IO.FileWriteAllText()ReadAllText()的 static 方法。

The following queries the https://loripsum.net/ API to get random paragraphs and writes to a file using the iso-8859-1 encoding.以下查询https://loripsum.net/ API 以获取随机段落并使用iso-8859-1编码写入文件。 Then reads that files and writes a copy using the same encoding and lastly compares both file hashes.然后读取该文件并使用相同的编码写入副本,最后比较两个文件哈希。 As you can see reading and writing is all done as a one-liner.正如你所看到的,阅读和写作都是单线完成的。

The using statements can be removed but you would need to use the Full Type Names.可以删除using语句,但您需要使用完整类型名称。

Set location to a temporary folder for testing.将位置设置为临时文件夹以进行测试。

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