I have a workflow where I try to do the following:
- A method accepting a callback, which internally produces a
Stream
and the callers of that method can use the callback to process theStream
whichever way they want - In one particular case, the caller is using the callback to produce an
IAsyncEnumerable
from the Stream.
I have created a minimal reproducing example below:
class Program
{
private static async Task<Stream> GetStream()
{
var text =
@"Multi-line
string";
await Task.Yield();
var bytes = Encoding.UTF8.GetBytes(text);
return new MemoryStream(bytes);
}
private static async Task<T> StreamData<T>(Func<Stream, T> streamAction)
{
await using var stream = await GetStream();
return streamAction(stream);
}
private static async Task StreamData(Func<Stream, Task> streamAction)
{
await using var stream = await GetStream();
await streamAction(stream);
}
private static async IAsyncEnumerable<string> GetTextLinesFromStream(Stream stream)
{
using var reader = new StreamReader(stream);
var line = await reader.ReadLineAsync();
while (line != null)
{
yield return line;
line = await reader.ReadLineAsync();
}
}
private static async Task Test1()
{
async Task GetRecords(Stream str)
{
await foreach(var line in GetTextLinesFromStream(str))
Console.WriteLine(line);
}
await StreamData(GetRecords);
}
private static async Task Test2()
{
await foreach(var line in await StreamData(GetTextLinesFromStream))
Console.WriteLine(line);
}
static async Task Main(string[] args)
{
await Test1();
await Test2();
}
}
Here, method Test1
works fine, whereas Test2
does not, failing with Stream is not readable
. The problem is that in the second case when the code gets to processing the actual stream, the stream is already disposed.
Presumably the difference between the two examples is that for the first one, reading the stream is performed while still in the context of the disposable stream
, whereas in the second case we are already out.
However, I would argue the second case could be valid too - at least I feel it's quite C#-idiomatic. Is there anything I am missing to get the second case to work too?