[英]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 finally
块try
/ 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.File
、 WriteAllText()
和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.