2

I have an async method that processes a datareader with a delegate function that is passed to it. The purpose of the delegate is to construct domain objects out of the reader and yield those back to the caller. I would like an intermediate method that constructs the delegate and reader, and returns the resulting IAsyncEnumerable from the called method. The only way I was able to make this happen is to actually consume the IAsyncEnumerable and yield those results from the intermediate method. Attempting to just return directly results in a compiler error stating I must use yield return or yield break.

delegate T ProcessFunc<T>(MySqlDataReader reader);

async IAsyncEnumerable<T> ProcessReader<T>(MySqlDataReader reader, ProcessFunc<T> transformFunc)
{
    while (await reader.ReadAsync() != false)
    {
        yield return transformFunc(reader);
    }

    await reader.DisposeAsync();
}

public async IAsyncEnumerable<DataObject> GetDataObjectsAsync()
{
    ProcessFunc<DataObject> processFunc = (reader) =>
    {
        var id = reader.GetGuid( "id" );

        return new DataObject(id);
    };

    var reader = await GetDataObjectsReaderAsync(); //Constructs appropriate sqlcommand and returns a mysqldatareader

    //only way i can get this to function
    //would like to just be able to write: return ProcessReader(reader, processFunc)
    //so as not to chain another enumerable
    await foreach (var obj in ProcessReader( reader, processFunc ))
        yield return obj; 
}
cubesnyc
  • 1,385
  • 2
  • 15
  • 32
  • 2
    Related: [Pass-through for IAsyncEnumerable?](https://stackoverflow.com/questions/59876417/pass-through-for-iasyncenumerable) – Theodor Zoulias Jun 14 '20 at 22:19

1 Answers1

1

In this case you can change your ProcessReader to accept Task<MySqlDataReader> instead of MySqlDataReader so you can make your GetDataObjectsAsync synchronous:

async IAsyncEnumerable<T> ProcessReader<T>(Task<MySqlDataReader> readerTask, ProcessFunc<T> transformFunc)
{
    var reader = await readerTask;
    while (await reader.ReadAsync() != false)
    {
        yield return transformFunc(reader);
    }

    await reader.DisposeAsync();
}

public IAsyncEnumerable<DataObject> GetDataObjects()
{
    ProcessFunc<DataObject> processFunc = (reader) =>
    {
        var id = reader.GetGuid( "id" );

        return new DataObject(id);
    };

    return ProcessReader(GetDataObjectsReaderAsync(), processFunc)
}

Or change your GetDataObjectsAsync method to return Task<IAsyncEnumerable<DataObject>>:

public async Task<IAsyncEnumerable<DataObject>> GetDataObjectsAsync()
{
    ProcessFunc<DataObject> processFunc = (reader) =>
    {
        var id = reader.GetGuid( "id" );

        return new DataObject(id);
    };

    var reader = await GetDataObjectsReaderAsync(); //Constructs appropriate sqlcommand and returns a mysqldatareader

    return ProcessReader(reader, processFunc); 
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • is this merely a function of the signature being async IAsyncEnumerable ? – cubesnyc Jun 14 '20 at 19:47
  • @cubesnyc if I understood your question correctly - yes, it is because your method is decalred to return `IAsyncEnumerable` and marked as `async`. – Guru Stron Jun 14 '20 at 19:59