Reputation: 52270
I am wondering how to write chainable, async extension methods without requiring the caller to write multiple awaits and nested parentheses.
Example. Let's say your goal is for the caller to be able to write this sort of snippet:
var example = new MyCompilableClass();
await example.Compile().Run();
(Note: I'm not writing a compiler. I am just using these names to make it clear that one has to happen before the other).
To support the above, you create two interfaces:
public interface ICompilable
{
Task<IRunnable> CreateExecutableImage();
}
public interface IRunnable
{
Task Execute();
}
You implement them as async:
class SourceCode : ICompilable
{
public async Task<IRunnable> CreateExecutableImage()
{
await Stub.DoSomethingAsynchronous();
return new ObjectCode();
}
}
class ObjectCode : IRunnable
{
public async Task Execute()
{
await Stub.DoSomethingAsynchronous();
}
}
And then write the two extension methods with appropriate type constraints:
static class ExtensionMethods
{
public static async Task<IRunnable> Compile<T>(this T This) where T : ICompilable
{
return await This.CreateExecutableImage();
}
public static async Task Run<T>(this T This) where T : IRunnable
{
await This.Execute();
}
}
So now the caller tries to compile his code. But we get an error on this line:
await example.Compile().Run(); //Does not compile
Here is the compilation error:
The type 'System.Threading.Tasks.Task' cannot be used as type parameter 'T' in the generic type or method 'ExtensionMethods.Run(T)'. There is no implicit reference conversion from 'System.Threading.Tasks.Task' to 'Example.IRunnable'
We can fix the compilation error with parentheses:
(await example.Compile()).Run();
...or two lines of code:
var compiled = await example.Compile();
await compiled.Run();
...which both work. But that seems rather unfortunate if you were looking forward to a clean, chainable syntax as we have with LINQ.
Is there a different way to implement these extension methods, so that they keep their asynchronous nature, but without requiring the ugly syntax?
Here is a Link to DotNetFiddle if you'd like to work with my example code.
Upvotes: 2
Views: 992
Reputation: 52270
One simple answer is just to add another extension method that converts the Task<T>
to a T
, like this:
static class ExtensionMethods
{
public static async Task Run<T>(this T This) where T : IRunnable
{
await This.Execute();
}
public static async Task Run<T>(this Task<T> This) where T : IRunnable
{
////Await the task and pass it through to the original method
await (await This).Execute();
}
}
This will enable the caller to use
await example.Compile().Run();
...although he may have no idea he is passing the task, not the result, to Run()
(unless he really thinks about it). Shouldn't matter to him.
Upvotes: 1