Error with duck enumeration for IAsyncEnumerable in System.Interactive.Async

1.2k Views Asked by At

I wonder why this code for IAsyncEnumerable<>

dynamic duckAsyncEnumerable = new int[0].ToAsyncEnumerable();
var duckAsyncEnumerator = duckAsyncEnumerable.GetEnumerator();

raises an exception:

'object' does not contain a definition for 'GetEnumerator'

Same code for IEnumerable<> works fine. Moreover inplementation for IAsyncEnumerable<> via reflection works fine too. Reproduced in .NET and .NET Core.

This code needed for IOutputFormatter implementation that get source data as object and have to iterate through it.

described example in dotnetfiddle

2

There are 2 best solutions below

4
On BEST ANSWER

Calling new int[0].ToAsyncEnumerable() will return the (internal) type AsyncIListEnumerableAdapter<int>. This type implements among other things IEnumerable<int> so it has the method IEnumerable<int>.GetEnumerator(). However, it implements this method using explicit interface implementation.

An interface method that is explicitly implemented is not available when you call through dynamic (it is private). To access the method you will have to cast the reference to the interface first as explained in this answer to the question Use explicit interface implementations with a dynamic object.

0
On

I got the solution. An object have extension method ToAsyncEnumerable which returns IAsyncEnumerable<object>. Thus we can iterate over it:

public async Task Process(object source)
{
    using (var enumerator = source.ToAsyncEnumerable().GetEnumerator())
    {
        while (await enumerator.MoveNext())
        {
            var item = enumerator.Current;
        }
    }
}

One can create wrapper that takes IAsyncEnumerable<T> and implements IAsyncEnumerable<object>. Activator creates that wrapper in extension method. Here is the implementation:

public class AsyncEnumerable<T> : IAsyncEnumerable<object>
{
    private IAsyncEnumerable<T> _source;

    public AsyncEnumerable(IAsyncEnumerable<T> source)
    {
        _source = source;
    }

    public IAsyncEnumerator<object> GetEnumerator()
    {
        return new AsyncEnumerator<T>(_source.GetEnumerator());
    }
}

public class AsyncEnumerator<T> : IAsyncEnumerator<object>
{
    private IAsyncEnumerator<T> _source;

    public AsyncEnumerator(IAsyncEnumerator<T> source)
    {
        _source = source;
    }

    public object Current => _source.Current;

    public void Dispose()
    {
        _source.Dispose();
    }

    public async Task<bool> MoveNext(CancellationToken cancellationToken)
    {
        return await _source.MoveNext(cancellationToken);      
    }
}

public static class AsyncEnumerationExtensions
{
    public static IAsyncEnumerable<object> ToAsyncEnumerable(this object source)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }
        else if (!source.GetType().GetInterfaces().Any(i => i.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>)))
        {
            throw new ArgumentException("IAsyncEnumerable<> expected", nameof(source));
        }            

        var dataType = source.GetType()
            .GetInterfaces()
            .First(i => i.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>))
            .GetGenericArguments()[0];

        var collectionType = typeof(AsyncEnumerable<>).MakeGenericType(dataType);

        return (IAsyncEnumerable<object>)Activator.CreateInstance(collectionType, source);
    }
}