Search code examples
c#visitor-patterniasyncenumerable

Looking for a better pattern for a visitor-IAsyncEnumerable?


Consider this snippet (much simplified than the original code):

    async IAsyncEnumerable<(DateTime, double)> GetSamplesAsync()
    {
       // ...

            var cbpool = new CallbackPool(
                HandleBool: (dt, b) => { },
                HandleDouble: (dt, d) =>
                {
                    yield return (dt, d);     //not allowed
                });

            while (await cursor.MoveNextAsync(token))
            {
                this.Parse(cursor.Current, cbpool);
            }
    }

    private record CallbackPool(
        Action<DateTime, bool> HandleBool,
        Action<DateTime, double> HandleDouble
        );

Then, the below Parse is just a behavior-equivalent of the original.

    Random _rnd = new Random();
    void Parse(object cursor, CallbackPool cbpool)
    {
        double d = this._rnd.NextDouble();  //d = [0..1)
        if (d >= 0.5)
        {
            cbpool.HandleDouble(new DateTime(), d);
        }
        else if (d >= 0.25)
        {
            cbpool.HandleBool(new DateTime(), d >= 0.4);
        }
    }

However, I do like the GetSamplesAsync code, but the compiler does not: the yield cannot be used within a lambda.

So, I changed the function as follows, although it became much less readable (and also error-prone):

    async IAsyncEnumerable<(DateTime, double)> GetSamplesAsync()
    {
       // ...

            (DateTime, double) pair = default;
            bool flag = false;
            var cbpool = new CallbackPool(
                HandleBool: (dt, b) => { },
                HandleDouble: (dt, d) =>
                {
                    pair = (dt, d);
                    flag = true;
                });

            while (await cursor.MoveNextAsync(token))
            {
                this.Parse(cursor.Current, cbpool);
                if (flag)
                {
                    yield return pair;
                }
                flag = false;
            }
    }

I wonder if there is a better way to solve this kind of pattern.


Solution

  • The external flag/pair is pretty dangerous and unnecessary (and it forces a closure); it seems like this bool could be returned from the Parse method, for example:

    await foreach (var item in cursor)
    {
        if (Parse(item, cbpool, out var result))
            yield return result;
    }
    

    (everything could also be returned via a value-tuple if you don't like the out)