3

I have a simple scenario where I have a class with the following method:

public async IAsyncEnumerable<Entity> GetEntities(IQueryOptions options){
  if(!validator.ValidateQuery(options)) { throw new ArgumentException(nameof(options));}

  var data = dataSource.ReadEntitiesAsync(options);

  await foreach (var entity in data) { yield return await converter.ConvertAsync(entity);}
}

Is it possible to have the ArgumentException thrown exactly at the GetEntities() method call, and not after first step of iteration like here:

await foreach(var e in GetEntities(options)) { // some code here }

I'm asking because when I want to return IAsyncEnumerable up to my API controller the exception actually is thrown in the framework code. I have no chance to catch it, and return a HTTP 404 BAD REQUEST code. Surely I can intercept exceptions in the request pipeline, but sometimes I want to wrap them in other exceptions depending on the abstraction layer they come from.

Michael P
  • 670
  • 7
  • 23
  • Why don't you validate before calling `GetEntities`? Enumerators aren't for throwing exceptions – Camilo Terevinto Aug 19 '20 at 19:02
  • 1
    You can write a wrapper method that is not `async` and performs the validation synchronously, before returning the result of the `async` method. Whether this meaningfully shifts the moment of the exception depends on how exactly `await foreach` goes about its business, though (I don't know). – Jeroen Mostert Aug 19 '20 at 19:05
  • @JeroenMostert Yes, it is actually the workaround I currently use. Anyway sometimes it leads to such constructs that some interface methods are effectively not shielded against invalid arguments or nulls. – Michael P Aug 19 '20 at 19:11
  • 1
    Related: [Method having yield return is not throwing exception](https://stackoverflow.com/questions/42149895/method-having-yield-return-is-not-throwing-exception). `IEnumerable` and `IAsyncEnumerable` iterators are similar in that regard. – Theodor Zoulias Aug 19 '20 at 22:38

1 Answers1

1

Split it into two functions. Here's an example:

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
                    
public class Program
{
    public static async Task Main()
    {
        var enumeration = AsyncEnumeration(-1);
        await foreach(int x in enumeration)
        {
            Console.WriteLine(x);
        }
    }
    
    public static IAsyncEnumerable<int> AsyncEnumeration(int count)
    {
        if (count < 1)
            throw new ArgumentOutOfRangeException();
        
        return AsyncEnumerationCore(count);
    }
    
    private static async IAsyncEnumerable<int> AsyncEnumerationCore(int count)
    {
        for (int i = 0; i < count; i++)
        {
            yield return i;
            await Task.Delay(1);
        }
    }
}
Joelius
  • 3,839
  • 1
  • 16
  • 36
  • 1
    Aside: you can put the `private` method inside the `public` method as a nested function. In that case it's even an option to make the nested method non-`static` and omit the `count` parameter altogether. (Whether to do is or not is largely a matter of taste, assuming for a moment this isn't some sort of performance hotspot where the alternatives have to be tested. It does make it neatly impossible to accidentally call the method without validation even from inside the class, though.) – Jeroen Mostert Aug 19 '20 at 19:12
  • Yea it's just normal functions, you can do whatever you want. It's only static in my example. – Joelius Aug 19 '20 at 19:17
  • Maybe it is a case for another question, but: Is this solution viable for IAsyncEnumerable where the check takes place in an async method too? – Michael P Sep 02 '20 at 13:33
  • But I didn't test it. I think if you don't use yield, it won't make an state-machine. – Joelius Sep 03 '20 at 05:20