Search code examples
c#ienumerableidisposable

"using" blocks with IAsyncEnumerable


In my repository code, I am getting a disposable instance of Database. I am then using this to return an IAsyncEnumable of my object type. The problem I'm running into is that the database object is being disposed before the enumeration happens at all -- so the connection is closed from under it. What is the pattern to solve this? (if it matters -- which it shouldn't -- this is NPoco).

I am editing the problem to say that it is specific to IAsyncEnumerable such that the awaited row-by-row fetch is more appropriate in this context, as opposed to assembling an entire List of results and returning that at once.

    public IAsyncEnumerable<T_AccountViewProperty> RetrieveManyAsync(AccountViewId input)
    {
        var database = GetDatabase();
        return database.Query<T_AccountViewProperty>()
            .Where(x => x.AccountViewId == (int)input)
            .ToEnumerableAsync()
            .DisposeWhenCompleted(database);     // <-- is this a thing?
    }

Solution

  • No, DisposeWhenCompleted(database) isn't a thing. But it could be, if you write an extension method for it. For example:

    public static async IAsyncEnumerable<T> DisposeWhenCompleted<T>(
        this IAsyncEnumerable<T> source,
        IDisposable disposable)
    {
        using (disposable)
        {
            await foreach (T t in source)
            {
                yield return t;
            }
        }
    }
    

    That would do just what you want. That said, I find that the using statement is better when used explicitly. The above construct isn't nearly as clear, IMHO, as just putting the using in the method itself, even though the latter is more verbose:

    public IAsyncEnumerable<T_AccountViewProperty> RetrieveManyAsync(AccountViewId input)
    {
        using (var database = GetDatabase())
        {
            var result = database.Query<T_AccountViewProperty>()
                .Where(x => x.AccountViewId == (int)input)
                .ToEnumerableAsync()
    
            await foreach (T_AccountViewProperty x in result)
            {
                yield return x;
            }
        }
    }
    

    Another nice thing about doing it as above is that it's more efficient, especially when you want to dispose more than one item (if you have multiple items, you can just chain the DisposeWhenCompleted() method calls, but that creates a new iterator for each item that needs disposing).

    Just like with regular iterator methods, the C# compiler will ensure when it rewrites the code as a state machine that the implicit finally that the using block creates isn't executed until the iteration has actually completed. And this is in fact also true for regular async methods as well, which like both the original iterator methods and the new async iterator methods, have the same property that the method returns to the caller before the code in the method is actually done.

    Which makes sense, since both regular async methods and their iterator counterparts are essentially progeny of the original iterator methods. All three kinds of methods share the same basic concept of rewriting the method into a state machine.