12

I'm really enjoying working with C# 5.0 asynchronous programming. However, there are a few places where updating old code to be consistent with the TAP model is causing problems for me.

Here's one of them - I'm not sure exactly why Task<TResult> is not covariant in TResult, but it's causing problems for me when trying to update a covariant interface to move from a synchronous to an asychronous pattern:

Old code:

public interface IInitializable<out T> // ** out generic modifier **
{
    /// <summary>
    /// Boolean to indicate if class is ready
    /// </summary>
    bool IsInitialized { get; }

    /// <summary>
    /// Calls for instance to be initialized using current parameters
    /// Driver initialization can be done in the default constructor if desired
    /// </summary>
    T Initialize();
}

New code (won't compile):

public interface IAsyncInitializable<out T> // ** out generic modifier...broken **
{
    /// <summary>
    /// Boolean to indicate if class is ready
    /// </summary>
    bool IsInitialized { get; }

    /// <summary>
    /// Calls for instance to be initialized using current parameters
    /// Driver initialization can be done in the default constructor if desired
    /// </summary>
    Task<T> InitializeAsync(); // ** breaks because Task<TResult> is invariant in TResult **
}

Is there is a reasonable way around this without modifying my APIs too drastically? (Bonus: why is Task not covariant?). There's no IAwaitable interface, but I suppose I could make one and create an extension method that converts to a wrapped, covariant, awiatable task object. Or am I doing it wrong?

binki
  • 7,754
  • 5
  • 64
  • 110
lightw8
  • 3,262
  • 2
  • 25
  • 35
  • 2
    BTW, even if `Task` was a covariant interface, your code wouldn't compile. The correct version would be `Task InitializeAsync();` (without the `out` modifier on that line). – svick Aug 30 '12 at 21:23
  • An excellent point. I'd corrected that in VS, but forgot to edit SO. – lightw8 Aug 30 '12 at 21:25

2 Answers2

14

Task<T> can't be covariant in T, because it's a class. Only interfaces and delegates can have generic variance.

As for whether it's worth doing the wrapping... I guess that depends on how much you use the covariance within your project. I suspect you'll find all the wrapping and unwrapping confusing over time, to be honest - if it's not too bad to just take the hit of removing the covariance, I'd do that.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks for the advice! I implemented IAwaitable(out TResult) using your Eduasync TaskAwaiter(T), and can see that use of a separate interface is definitely not worth the loss of readability/usability. And, great point re: classes and generic variance...honored to have my mistake pointed out by @Jon Skeet :). No ITask(out T) is a bummer, though. I see Stephen Cleary's comment here (http://stackoverflow.com/questions/8407227/async-generic-delegate-in-c-sharp-5-0), but WinRT interop compatibility is a little bit unsatisfying as a justification for using the concrete type. – lightw8 Aug 30 '12 at 21:23
  • 1
    @DavidCuccia: You should read up on Stephen Toub's blog about propagating contexts when it comes to awaiters. It gets a bit trickier than Eduasync shows :( – Jon Skeet Aug 30 '12 at 21:25
  • @JonSkeet - Any idea why they framework developers didn't add an `ITask` and only implemented a class? – Philip Pittle Dec 13 '16 at 01:22
  • @PhilipPittle: For one thing, `Task` came along before async/await - it was designed mostly for parallelism. There are various things that might have been different if it had been designed from scratch now. – Jon Skeet Dec 13 '16 at 07:12
8

I believe that not including compiler support for the async keyword on an ITask interface was a major oversight on Microsoft's part. Luckily it's not too hard to work around this limitation.

I have implemented a covariant awaitable ITask<out TResult> interface. Its usage is pretty straightforward.

More information can be found at:

https://github.com/jam40jeff/ITask

jam40jeff
  • 2,576
  • 16
  • 14
  • Very nice work indeed. The use of generalized async return types in your library was highly educational. – spender Jul 09 '18 at 22:45