Reputation: 81
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.
Upvotes: 0
Views: 315
Reputation: 20373
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();
Upvotes: 2
Reputation: 10790
Func<T>
's type argument is not contravariant and Task<T>
's type argument is not covariant. So you can not do these :
public class Base
{
}
public class Derived : Base
{
}
public static void Main()
{
Func<Task<Base>> d = GetBase;
Func<Task<Derived>> e = d; // Will fail
Task<Base> dd = GetDerived(); // Will fail
}
public static Task<Base> GetBase()
{
return Task.FromResult(new Base());
}
public static Task<Derived> GetDerived()
{
return Task.FromResult(new Derived());
}
What is covariance and contravariance
What you can do is to change ConsultFormVM.CreateAsync()
methods return type to Task<BaseVM>
.
Upvotes: 1