50

I have an interface which is written like this:

public interface IItemRetriever
{
    public IAsyncEnumerable<string> GetItemsAsync();
}

I want to write an empty implementation that returns no item, like so:

public class EmptyItemRetriever : IItemRetriever
{
    public IAsyncEnumerable<string> GetItemsAsync()
    {
       // What do I put here if nothing is to be done?
    }
}

If it was a plain IEnumerable, I would return Enumerable.Empty<string>();, but I didn't find any AsyncEnumerable.Empty<string>().

Workarounds

I found this which works but is quite weird:

public async IAsyncEnumerable<string> GetItemsAsync()
{
    await Task.CompletedTask;
    yield break;
}

Any idea?

elmer007
  • 1,412
  • 14
  • 27
cube45
  • 3,429
  • 2
  • 24
  • 35

3 Answers3

66

If you install the System.Linq.Async package, you should be able to use AsyncEnumerable.Empty<string>(). Here's a complete example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        IAsyncEnumerable<string> empty = AsyncEnumerable.Empty<string>();
        var count = await empty.CountAsync();
        Console.WriteLine(count); // Prints 0
    }
}
Shad
  • 4,423
  • 3
  • 36
  • 37
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 4
    @cube45: I would generally regard `System.Linq.Async` as "virtually part of the framework". There's very little that's *just* in netstandard2.1 when it comes to `IAsyncEnumerable`. – Jon Skeet Dec 22 '19 at 11:58
  • @cube45 I would be careful about not using the package there are many quarks with async flows you will discover when you start using it more unless you really know what you are doing I would really on the nugget. – Filip Cordas Dec 22 '19 at 12:15
  • Thanks for your answers. I never used IAsyncEnumerable before, and I was just experimenting, not doing something "for real". You're probably right, the package might be useful. – cube45 Dec 22 '19 at 13:51
  • There is a problem if it is being used with efcore https://github.com/dotnet/efcore/issues/18124 – Pavel Shastov Feb 18 '20 at 09:18
  • 2
    Is there another way to get it? When I've installed the Linq.Async nuget, all calls to "Where" begin to be ambiguous call from AsyncEnumerable and IQueryable (from Entity Framework Core). – Iúri dos Anjos Apr 23 '20 at 19:07
  • @Iúri dos Anjos: You could explicitly call the right static method if you wanted - but I suggest you delve deeper into exactly what's going on. I haven't used EF Core for a long time so I can't comment. This may well be covered in another SO question, and if it's not, I suggest you ask one with a complete example and results of your investigations. – Jon Skeet Apr 24 '20 at 05:51
  • Because I'm using the conde in my infrastructure, I can't use any 3th party. – Mohammad Mirmostafa Feb 06 '22 at 13:24
22

If for any reason you don't want to install the package which is mentioned in Jon's answer, you can create the method AsyncEnumerable.Empty<T>() like this:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public static class AsyncEnumerable
{
    public static IAsyncEnumerator<T> Empty<T>() => EmptyAsyncEnumerator<T>.Instance;

    class EmptyAsyncEnumerator<T> : IAsyncEnumerator<T>
    {
        public static readonly EmptyAsyncEnumerator<T> Instance = 
            new EmptyAsyncEnumerator<T>();
        public T Current => default!;
        public ValueTask DisposeAsync() => default;
        public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(false);
    }
}

Note: The answer doesn't discourage using the System.Linq.Async package. This answer provides a brief implementation of AsyncEnumerable.Empty<T>() for cases that you need it and you cannot/don't want to use the package. You can find the implementation used in the package here.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Thanks for your answer. Indeed, that would be an option too. I think that I'd prefer that way rather than installing another package. I'll mark this one as accepted. Nitpick : You say "extension method" while it's just a static method in a static class. – cube45 Dec 22 '19 at 11:53
  • 1
    @cube45: So are you not planning on using any LINQ functionality with the asynchronous sequences involved? Because as soon as you want to do anything you'd normally do with synchronous LINQ, you'll need System.Linq.Async. – Jon Skeet Dec 22 '19 at 11:58
  • 3
    This code works to return an `IAsyncEnumerator`, but not an `IAsyncEnumerable` which is what the OP was asking for. In order to accomplish that, you need to add the `IAsyncEnumerable` to your `EmptyAsyncEnumerator` class, then include the `GetAsyncEnumerator` method from the code you linked. – Tobias J Apr 14 '21 at 22:22
10

I wanted to avoid installing System.Linq.Async (due to its issues with namespace collisions) but the previous answer does not actually implement IAsyncEnumerable<T> as requested in the original question. Here's a full solution which implements that interface to easily allow calling AsyncEnumerable.Empty<T> in the same way that Enumerable.Empty<T> works today.

public static class AsyncEnumerable
{
    /// <summary>
    /// Creates an <see cref="IAsyncEnumerable{T}"/> which yields no results, similar to <see cref="Enumerable.Empty{TResult}"/>.
    /// </summary>
    public static IAsyncEnumerable<T> Empty<T>() => EmptyAsyncEnumerator<T>.Instance;

    private class EmptyAsyncEnumerator<T> : IAsyncEnumerator<T>, IAsyncEnumerable<T>
    {
        public static readonly EmptyAsyncEnumerator<T> Instance = new EmptyAsyncEnumerator<T>();
        public T Current => default;
        public ValueTask DisposeAsync() => default;
        public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return this;
        }
        public ValueTask<bool> MoveNextAsync() => new ValueTask<bool>(false);
    }
}
Tobias J
  • 19,813
  • 8
  • 81
  • 66
  • "I wanted to avoid installing a new package" This will only lead you to reinventing the wheel (and most of the time, probably inventing worse wheels than the ones that exist). I don't personally think it is a good mentality overall. – julealgon Jun 10 '22 at 13:02
  • 5
    @julealgon there are definitely times where that may be true but it's not always that simple. In this case, `System.Linq.Async` is notorious for causing namespace collisions with EF Core which were only fixed w/ version 6.0. It wasn't worth the effort "fixing" all of the namespaces throughout my solution just to get that one method. I've updated the answer to reflect that. – Tobias J Jun 10 '22 at 16:42
  • 1
    Fair enough, in context it is more sensible now (I'm aware of the collisions with EF as well). I was arguing against "coding your own" being the _default_ approach which is what your original answer implied. Thanks for clarifying. – julealgon Jun 10 '22 at 17:25