2

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

Beetee
  • 475
  • 1
  • 7
  • 18
Weerel
  • 23
  • 5

2 Answers2

2

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.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • As I undastand there is no way to call explicit implementations in dynamic vars. In my case there is the only way to use reflection. Maby alternative implicit implementation for ToAsyncEnumerable is the option? Is it an issue for system.interactive? Especially when there is no non generic interface IAsyncEnumerable. – Weerel Sep 08 '17 at 11:28
  • @Weerel: Why do you have to use reflection? You can use the `as` operator: `var asyncEnumerable = duckAsyncEnumerable as IAsyncEnumerable;` and then proceed if `asyncEnumerable` isn't null. If you really want to use duck typing you can combine this with the `dynamic` approach. Using `dynamic` is mostly asking the compiler to generate reflection code on your behalf anyway. – Martin Liversage Sep 08 '17 at 11:55
  • The problem is I don't actually know type of `IAsyncEnumerable>`, `int` just an example. I get it as `object` with any underlying type. I understand that non reflection/dynamic way is preferable option. – Weerel Sep 08 '17 at 12:10
  • @Weerel: Using `dynamic` is mostly compiler generated reflection which makes it easier to do but not more efficient. It will only resolve public members. You can write your own reflection code to resolve private members. – Martin Liversage Sep 08 '17 at 12:13
0

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);
    }
}
Weerel
  • 23
  • 5