Powershell: PS implementation of the C# Using statement

Created on 13 Jun 2019  路  16Comments  路  Source: PowerShell/PowerShell

Summary of the new feature/enhancement

The C# language provides a convenient syntax that ensures the correct use of IDisposable objects. I believe this would be a great addition to the PowerShell language, as the 'Using statement' simplifies the code that you have to write to create a resource and then finally clean up the object.

Without the using statement you are required to ensure that Dispose() is called, then followed by the Close() method. By implementing the 'Using statement' you can assume that all kinds of streams are getting closed.

I would like to see the PowerShell language use a similar syntax to C#, which would be using (expression) statement.
PowerShell does make use of the $using variable, but I don't believe adding the 'using' keyword will cause any issues.

using ($read = [system.io.StreamReader]::new('C:\tmp\test.txt')) {
    $read.Read()
}
Issue-Enhancement

Most helpful comment

If only we had ?. then your finally statement becomes a bit more simpler $client?.Dispose(). Man, love me some C#. :-)

All 16 comments

Come to think of it, I think this has come up in discussions previously, where the idea that this could actually be a cmdlet as well surfaced. Compiled cmdlets already have the capability to properly implement disposable features, after all.

Do you think a cmdlet would be better than a keyword? It might be possible to have it be pipeline-capable if it's a cmdlet, which could be interesting, depending on implementation, and cmdlets are a little more flexible than keywords...

Also, if it's a cmdlet we could package it in a separate module and put it on the PSGallery, too, so even PS5.1 could make use of it as well. 馃

An interesting theory creating as a cmdlet. I'm still feeling its nicer if a keyword then it would be part of the language and get utilized more.

I think that makes sense. We do have the unfortunate consequence there of it being unavailable on downlevel powershell versions, though.

Also, keywords tend to be a bit less discoverable than cmdlets in general; keywords don't tab-complete, but functions and cmdlets do, and they're a bit easier to find in the help documents as well -- most language features are documented in a slew of about_* help topics and there can be some topics that cover multiple things, whereas each cmdlet gets its own discrete topic.

I'm not against a keyword, despite all that. Keywords have a really nice no-nonsense simplicity about them that can be really nice to work with. Cmdlets can tend towards feature creep, among other things, and tend to be a bit slower to operate.

I'm not really sure which approach is really better. Either way, though, we should have this implemented in some form or another. :smile:

I think there is a good argument for both to be fair. I do get the backward compatibility but maybe it also encourages the use of PS 7 with shiny new features! Interesting to hear other peoples thoughts on this as well.

It would be difficult to do using as a cmdlet without losing some safety. There's many situations where a pipeline stop exception could be thrown before the cmdlet received the IDisposable.

Last time this came up, @BrucePay suggested this syntax:

Use-Object { [SomeDisposable]::new() } {
    $PSItem.DoSomethingWithDisposable()
}

That could work, but I'd still be worried that a pipeline stop would be triggered before the cmdlet finished invoking the script block (that's assuming that MshCommandRuntime.WriteObject checks for pipeline stops, which I think it does). If it was a keyword then the compiler could generate a safer expression. It also just doesn't look as nice imo for whatever that's worth.

Also, keywords tend to be a bit less discoverable than cmdlets in general

eh... if you know enough to know that something needs to be disposed, you probably know about using.

@Graham-Beer The using discussion comes every couple of years and then fades away. I suspect this is due to the fact that there really haven't a been lot of scenarios where you _need_ it in PowerShell and the few where it's required can be handled with try/finally. Otherwise the pipeline/cmdlets take care of disposing the resources for you. As a consequence, it has been hard to sustain much energy around this issue. I'm not against the idea (in fact I was quite keen on it at one point) but currently it's just kind of meh. So if you have some new scenarios where using is critical, please share them. Thanks!

just kind of meh

That sums it up nicely for me as well.

Does a finally block run in a cmdlet/function even in the case of a pipeline stop exception?

As for the lack of sustained interest... I think the lack of decent using solution makes streams in PS awkward and discourages their use. I'm a HUGE fan of streams and have always felt they are a missed opportunity in PS. I have used them heavily in PHP, C#, and Python. Because they are clunky in PS, I will either Add-Type some C# in or write a library in C# to handle it for me. In a pinch I can use a try/finally, but it starts to get unruly and painful to read when you have multiple IDisposables to work with at once.

here's an example from an earlier issue:

function Get-RemoteCertificate {
  [CmdletBinding()]
  [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate])]
  param (
    [Parameter(
      Mandatory,
      ValueFromPipeline
    )]
    [ValidateNotNull()]
    [Uri]
    $Uri
  )
  process {
    try {
      $TcpClient = [System.Net.Sockets.TcpClient]::new($Uri.Host, $Uri.Port)
      try {
        $SslStream = [System.Net.Security.SslStream]::new($TcpClient.GetStream())
        $SslStream.AuthenticateAsClient($Uri.Host)
        $SslStream.RemoteCertificate
      } finally {
        $SslStream.Dispose()
      }
    } finally {
      $TcpClient.Dispose()
    }
  }
}

@markekraus

Does a finally block run in a cmdlet/function even in the case of a pipeline stop exception?

Yes, with some caveats. If you place the assignment inside the try, there could be an exception or stop prior to the assignment. If this happens in the try, the finally could still fire, potentially calling Dispose on an object from a previous scope. If you place the assignment outside the try, a stop could occur before getting to the try block.

So I've taken to a pattern like this:

$client = $null
try {
    $client = [TcpClient]::new($uri.Host, $uri.Port)
} finally {
    if ($null -ne $client) {
        $client.Dispose()
    }
}

It's not pretty, but it's safe.

oof... that's even more painful.

I have a PR in the works (#9900) to make this much easier for pipeline cmdlets (esp. those that need to keep the object around for the entire process{} sequence and only dispose it at the very end), but that scoping issue may well still be a bit of a problem. Not sure there's a neat solution to that one. 馃

That's honestly one pretty clear case for a using keyword, I must say...

If only we had ?. then your finally statement becomes a bit more simpler $client?.Dispose(). Man, love me some C#. :-)

I quite like the C# syntax of ?, @rkeithhill. How do we push this topic idea forward?

I think there may be an existing issue for it, but if not create one -- if there already is one, add a comment.

If there is an open issue for the ?. / safe navigation operator, could you link it here please? I would very much like to upvote!

@Cirzen #3240 includes it.

Was this page helpful?
0 / 5 - 0 ratings