Search code examples
c#async-awaitfunc

Cannot convert anonymous method block to delegate because some of the return types in the block are not implicitly convertible


I want to refactor some functions that create asynchronously ViewModels. They were looking as follow :

public async Task NavigateToCreateFormVM()
{
    try
    {
        IsLoading = true;
        CurrentVM = await Task.Run(() => new CreateFormVM());
        <...>
        IsLoading = false;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "App", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

So in order to refactor all the logic, I tried writing a delegate to keep the code easy t maintain.

public async Task NavigationDelegate(Func<Task<BaseVM>> delegate)
{
    try
    {
        IsLoading = true;
        CurrentVM = await delegate();
        <...>
        IsLoading = false;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "App", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

and for example change my functions to :

public async Task NavigateToCreateFormVM()
{
    await NavigationDelegate(() => Task.Run(() => new CreateFormVM()));
}

public async Task NavigateToConsultFormVM()
{
    await NavigationDelegate(() => ConsultFormVM.CreateAsync()); // type of ConsultFormVM
}

But I get two error messages indicating that they can't convert CreateFormVM/ConsultFormVM to BaseVM (they both inerith from BaseVM). If i change NavigationDelegate(Func<Task<BaseVM>> delegate) to NavigationDelegate(Func<Task<CreateFormVM>> delegate) it works with no problems.

The errors messages are CS0029 and CS1662.

Cannot implicitly convert type 'System.Threading.Tasks.Task<Project.CreateFormVM>' to 'System.Threading.Tasks.Task<Project.BaseVM>' Cannot convert anonymous method block to delegate type because some of the return types in the block are not implicitly convertible to the delegate return type

I don't know if I'm missing something or if I did something wrong (or if it shouldn't be done this way at all ?) so I'd be grateful if soemone knew how to resolve my problem.


Solution

  • As @Eldar points out, TResult is invariant in Task<TResult>, as with all generic classes in .Net.

    This means that you can't pass a function that returns Task<CreateFormVM> in place of a Func<Task<BaseVM>>.

    However, there is a way to define NavigationDelegate in a way that will achieve your desired behaviour, using generic type constraints:

    public async Task NavigationDelegate<TBaseVM>(Func<Task<TBaseVM>> delegate)
    where TBaseVM : BaseVM
    {
        try
        {
            IsLoading = true;
            CurrentVM = await delegate();
            <...>
            IsLoading = false;
        }
        catch (Exception ex)
        {
            MessageBox.Show(
                ex.Message,
                "App",
                MessageBoxButton.OK,
                MessageBoxImage.Error);
        }
    }
    

    Now you can pass any function that returns a Task<TResult>, who's TResult derives from TBaseVM.

    Including this:

    public async Task NavigateToCreateFormVM()
    {
        await NavigationDelegate(() => Task.Run(() => new CreateFormVM()));
    }
    

    The constraint ensures that result of awaiting the delegate will be assignable to BaseVM, meaning this is safe:

    CurrentVM = await delegate();