3

I have a class with a resource that ideally should be disposed of using an async method, and I'm trying to use a Using statement to do so.

Luckily, .NET added the IAsyncDisposable interface as well as a nice doc explaining how to Implement a DisposeAsync method. The doc says:

The public parameterless DisposeAsync() method is called implicitly in an await using statement

Easy! Except... there's just one problem: I'm using VB.NET, not C#. I can't find any documentation on how to use this feature in VB.NET, or anyone asking after it. VB.NET (using .NET 6) will only allow a Using statement for an instance of IDisposable, and if both IDisposable and IAsyncDisposable are implemented, it only calls Dispose, not DisposeAsync. I can't find any equivalent Await Using statement for VB.NET.

Is there any way for me to properly leverage the IAsyncDisposable interface in VB.NET with a Using statement? Or have I finally reached the point where Microsoft's abandonment of the language has caught up with me?

Keith Stein
  • 6,235
  • 4
  • 17
  • 36
  • 1
    _"Or have I finally reached the point where Microsoft's abandonment of the language has caught up with me?"_ - probably – Dai Aug 09 '23 at 13:59
  • 1
    I tried hacking something together using `Async` extension-methods, with `Try/Finally` and callback-hell, but apparently unlike C#, VB.NET (_still_) doesn't allow `Await` (for `Await disposable.DisposeAsync()`) inside a `Finally` block - [there are workarounds from when C# had the same limitation](https://stackoverflow.com/questions/16626161/a-good-solution-for-await-in-try-catch-finally) which will work with VB.NET, but it got too tedious for me and I gave up, sorry). So I think this is a strong sign that it's time to more seriously migrate away from VB.NET. – Dai Aug 09 '23 at 14:19
  • There should be a previous question about this (though I searched and didn't find one, maybe the one I remember got deleted for some reason). The short answer is that there is no built-in support, so you have to write the operations that happen under-the-hood for `Using` explicitly. – Craig Aug 09 '23 at 14:26

1 Answers1

1
  • VB.NET (even as-of August 2023) does not support IAsyncDisposable in Using.
  • Nor does it support using Await inside Finally.
  • So the best you can do to try some ugly hack and/or port-over some old C# tricks...

Approach 1: Use C# for the using part:

Just create a new empty C# Class Library project with a single helper method which you can call from VB.NET, something like this:

(Just don't expect to be able to use ConfigureAwait(False) - even in C# it's hard).

public static class ArghExtensions
{
    public static async Task UsingAsyncDisposableAsync( this IAsyncDisposable subject, CancellationToken cancellationToken, Func<CancellationToken,Task> asyncBody ) // Need this parameter order for ergonomic reasons.
    {
        await using( subject )
        {
            // Argument validation (preconditions) needs to be done from within the implicit try/finally, otherwise `subject` won't be disposed if `asyncBody` is null.
            if( subject   is null ) throw new ArgumentNullException(nameof(subject));
            if( asyncBody is null ) throw new ArgumentNullException(nameof(asyncBody));
            
            await asyncBody( cancellationToken ).ConfigureAwait(false);
        }
    }
}

Then call it from VB.NET:

Imports ArghExtensions

Async Function FoobarAsync( cancellationToken As CancellationToken ) As Task

   Dim d AS IAsyncDisposable = GetSomethingDisposable()
   Await d.UsingAsyncDisposableAsync( Async Function( ct As CancellationToken )
    
        ' do async stuff in here
    
   End Function )

End Function

An alternative way of implementing this method, without using any C# step, is to use Reflection.Emit from within VB.NET to generate equivalent (and safe) IL that reimplements that UsingAsyncDisposableAsync method - though this will be a lot of work for probably little gain tbh.

Approach 2: Embrace VB.NET's verbosity

Instead of using any C# code, we can actually still do it all in VB.NET, with the caveat that you need to use Try/Catch and not Try/Finally as VB.NET still can't use Await inside Finally:

Async Function FoobarAsync( cancellationToken As CancellationToken ) As Task

    ' See https://stackoverflow.com/a/16626313/159145
    Dim capturedException As ExceptionDispatchInfo = Nothing
    Dim d AS IAsyncDisposable = GetSomethingDisposable()
    Try
    
        ' do async stuff in here, before the DisposeAsync call below.
        
        Await d.DisposeAsync().ConfigureAwait(False)
        d = Nothing
    
    Catch x As Exception
        
        capturedException = ExceptionDispatchInfo.Capture( x )
    
    End Try
   
    If capturedException IsNot Nothing Then
        
        If d IsNot Nothing Then
            Await d.DisposeAsync().ConfigureAwait(False)
            d = Nothing
        End If
        
        capturedException.Throw()
            
    End If

End Function

Function GetSomethingDisposable() As IAsyncDisposable
    
    Return New MemoryStream()
    
End Function

I haven't run this code through any static-analysis, but I'm confident this won't trigger CA2000

Dai
  • 141,631
  • 28
  • 261
  • 374