[英]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.