3

I have a function whose job is to execute an expression, then catch any exceptions and to rethrow another exception based on the caught one. There is also an async variant of this function.

I want to check in a unit test project (xUnit, if that matters) if this function works well, that is, it throws the correct exception.

But I can't find a nice way to do this, since it seems that in some cases the async implementation is called with non-async code, which results in the exception not being caught.

The problem can be demonstrated with the following code:

using System;
using System.Threading.Tasks;
                    
public class Program
{   
    public static void DoStuff(Action lambda)
    {
        Console.WriteLine("Doing stuff with Action");
        lambda();
    }
    
    public async static Task DoStuff(Func<Task> lambda)
    {
        Console.WriteLine("Doing stuff with Func<Task>");
        await lambda();
    }
    
    public async static Task Main()
    {
        DoStuff(() => throw new Exception("WOW")); // Here I get warning CS4014 AND the exception goes unnoticed
        DoStuff(void () => throw new Exception("WOW")); // Explicit void seems unnecessary
        await DoStuff(async () => await Task.Delay(1));
    }
}

The output is:

Doing stuff with Func<Task>
Doing stuff with Action
Unhandled exception. System.Exception: WOW
   at Program.<>c.<Main>b__2_1()
   at Program.DoStuff(Action lambda)
   at Program.Main()
   at Program.<Main>()

https://dotnetfiddle.net/46eGYb

I'm using C# 10.0 with .NET 6 and Visual Studio 2022.

Am I missing something or is it a bug in C#? I do understand that explicitly specifying the void return type solves the problem, but it looks like to me that it could be inferred correctly.

EDIT:

After some discussion in the comments, the lesson is that the type of the first lambda can't be inferred (it wouldn't be possible to store it in a var, for example). But despite all that, the code compiles in this case.

Edit 2: this explains: https://stackoverflow.com/a/54208293/2289372

...when the parameter is an anonymous method, it will always prefer the overload who's delegate (or expression) has a return value over one that has no return value. This will be true whether it's a statement lambda or expression lambda; it applies to any form of anonymous function.

  • You aren’t handling the async part. The exception is in the other context – Daniel A. White Jul 08 '22 at 13:30
  • 1
    So are you asking how to make sure that the right overload is being called in case of sync delegate? – Peter Csala Jul 08 '22 at 13:30
  • Yepp I know that the exception is in the other context. The question is why the async overload is called when I don't explicitly specify that the return type of the lambda is void? – Péter Trombitás Jul 08 '22 at 13:39
  • Either use `void () =>` as you did or store the parameter in a variable like `Action foo = () => throw new Exception("WOW");` and then `DoStuff(foo);` – Peter Csala Jul 08 '22 at 13:44
  • Or `DoStuff(() => { throw new Exception("WOW"); return;});` – Peter Csala Jul 08 '22 at 13:46
  • To be clear: yes, I know that this solves my problem. But to me it would be logical if I didn't have to do this. So `DoStuff(() => throw new Exception("WOW"));` would call the sync overload and `DoStuff(async () => throw new Exception("WOW"));` would call the async overload. Is it maybe a bug in C#? – Péter Trombitás Jul 08 '22 at 13:47
  • No, it is not a C# bug. How could you tell from this code what is the return type of this `() => throw new Exception("WOW")`? If you declare the `foo` as `var`, like `var foo = () => throw new Exception("WOW");` then it will generate compile time error. So, if you inline the delegate it will resolve it as `Func` that's why you have the warning. – Peter Csala Jul 08 '22 at 13:51
  • Ah ok - it is strange that even though the type can't be inferred, it _is_ inferred - and in some more complex cases I don't even get a warning. This has just caused me a whole day of debugging... Now I know :) – Péter Trombitás Jul 08 '22 at 13:54
  • @PéterTrombitás Rather than editing the question please leave a post and mark it as the answer – Peter Csala Jul 08 '22 at 15:59
  • So you could get around this problem by giving `void DoStuff(Action lambda)` a return type, even if it's just `bool` and always returns `true`. – Gabriel Luci Jul 08 '22 at 18:08

0 Answers0